# Domain Pitfalls **Domain:** Kimai dashboard widget plugin (Symfony bundle + d3.js heatmap) **Researched:** 2026-04-08 **Note:** Web search/fetch tools were unavailable. Findings are based on training data knowledge of Kimai, Symfony bundles, d3.js, and Nix+PHP environments. All findings marked with confidence levels accordingly. ## Critical Pitfalls Mistakes that cause rewrites or major issues. ### Pitfall 1: Kimai Major Version Breaking Changes in Plugin API **What goes wrong:** Kimai 2.x has changed its internal plugin API, event system, and widget rendering between releases without a formal deprecation cycle. A plugin built against one version silently breaks on the next. The `WidgetInterface`, dashboard rendering hooks, and Twig extensions have all shifted. **Why it happens:** Kimai is maintained primarily by one developer (Kevin Papst). The plugin API is not versioned independently from the application -- it evolves with Kimai core. **Consequences:** Plugin stops rendering, throws Symfony container errors, or silently disappears from the dashboard after a Kimai update. **Prevention:** - Pin to a specific Kimai version in composer.json (`"kimai/kimai": "^2.x"` with a tight constraint) - Read UPGRADING.md in the Kimai repo before each Kimai update - Write an integration test that boots the Symfony kernel with the plugin loaded -- this catches container/DI breakage immediately - Subscribe to Kimai releases (GitHub watch) for breaking change awareness **Detection:** Dashboard widget silently missing after update. Symfony cache clear errors. DI container compilation failures. **Confidence:** MEDIUM (based on Kimai's development history through early 2025) **Phase relevance:** Phase 1 (scaffold) -- lock version constraints early. ### Pitfall 2: Timezone Mismatch Between PHP and JavaScript **What goes wrong:** Kimai stores timestamps in UTC in the database. The PHP backend converts to the user's configured timezone for display. If your d3.js heatmap receives raw UTC timestamps and bins them into days client-side, days land in the wrong cells. A session logged at 23:30 Berlin time (21:30 UTC) shows up on the correct day in Kimai's timesheet but the previous day in your heatmap. **Why it happens:** The boundary between "server renders timezone-aware data" and "client renders timezone-aware data" is easy to get wrong, especially when aggregating by day. **Consequences:** Hours appear on wrong days. Totals per day are wrong. User loses trust in the widget immediately. **Prevention:** - Aggregate by day on the PHP side using the user's configured Kimai timezone (`$user->getTimezone()`) - Send pre-aggregated `{date: "2026-04-08", hours: 5.5, count: 3}` to the frontend -- never raw timestamps - The d3 heatmap should receive date strings (not timestamps) so no further timezone conversion happens client-side **Detection:** Compare heatmap day totals against Kimai's built-in weekly/monthly reports. Discrepancies = timezone bug. **Confidence:** HIGH (this is a universal time-tracking visualization issue, well-documented across domains) **Phase relevance:** Phase 1 (data layer) -- get the aggregation right before building the visualization. ### Pitfall 3: Kimai Widget System Assumptions **What goes wrong:** Kimai's dashboard widget system expects widgets to implement specific interfaces and register via Symfony service tags. Developers coming from generic Symfony bundle development wire things up as controllers/routes instead of using the widget system, resulting in a working page but not a dashboard widget. **Why it happens:** Kimai's widget system is Kimai-specific, not standard Symfony. Documentation is sparse. Developers cargo-cult from Symfony controller tutorials instead of studying existing Kimai plugins. **Consequences:** You build a standalone page that works at `/my-heatmap` but cannot embed in the Kimai dashboard. Rework required to fit the widget interface. **Prevention:** - Study existing Kimai plugins that provide dashboard widgets (e.g., `kimai/CalendarBundle`, built-in widgets in `src/Widget/`) - Implement `WidgetInterface` (or extend `AbstractWidget`) from the start - Register as a tagged Symfony service: `kimai.widget` - Do not create a standalone controller -- the widget renders within the dashboard's Twig template **Detection:** Widget does not appear on the dashboard. No errors, just absent. **Confidence:** MEDIUM (based on Kimai source structure through 2024-2025) **Phase relevance:** Phase 1 (scaffold) -- get the widget rendering on the dashboard before writing any d3 code. ## Moderate Pitfalls ### Pitfall 4: d3.js Bundle Size in Kimai's Asset Pipeline **What goes wrong:** Including all of d3.js (~500KB minified) for a calendar heatmap that only needs `d3-scale`, `d3-selection`, `d3-time`, and `d3-time-format`. Kimai uses Webpack Encore for asset compilation, and a full d3 import bloats the plugin's JS bundle unnecessarily. **Why it happens:** `import * as d3 from 'd3'` is the most common tutorial pattern. **Prevention:** - Import only needed d3 modules: `import { select } from 'd3-selection'; import { scaleQuantize } from 'd3-scale';` - This reduces the d3 footprint to ~50-80KB - Verify with Webpack Bundle Analyzer that tree-shaking works **Confidence:** HIGH (well-documented d3 best practice) **Phase relevance:** Phase 2 (visualization) -- set up imports correctly from the first d3 code. ### Pitfall 5: Nix + PHP + Composer Dev Environment Complexity **What goes wrong:** Getting a working Kimai instance inside a Nix devshell is non-trivial. Kimai requires PHP 8.1+, specific PHP extensions (intl, gd, mbstring, zip, xml), MySQL/MariaDB or SQLite, and Composer. Nix's PHP packaging sometimes has extension version mismatches, and Kimai's `composer install` may try to download packages that conflict with Nix's hermetic approach. **Why it happens:** PHP ecosystem tooling (Composer, PECL extensions) assumes a mutable system. Nix is immutable. These philosophies clash in subtle ways -- e.g., Composer's `post-install-cmd` scripts that try to write to vendor directories, or PHP extensions that need specific system libraries. **Consequences:** Days lost fighting the dev environment before writing any plugin code. Temptation to abandon Nix and use Docker instead. **Prevention:** - Use `pkgs.php83` (or latest) with extensions via `php.withExtensions` - Use SQLite for the dev database (simpler than spinning up MariaDB in Nix) - Consider a hybrid approach: Nix provides PHP + Composer + Node, but let Composer manage vendor/ normally (don't try to Nixify Composer dependencies) - Set `COMPOSER_HOME` to a writable temp directory - Pre-seed the database with a SQL fixture, not Kimai's interactive installer - Test the devshell setup as the very first task -- do not proceed to plugin code until `bin/console kimai:version` works **Detection:** `nix develop` fails to start, or Kimai throws "extension missing" errors at runtime. **Confidence:** MEDIUM (based on general Nix+PHP experience; Kimai-specific Nix setup is uncommon) **Phase relevance:** Phase 0 / Pre-phase -- this blocks everything else. Must be solved first. ### Pitfall 6: d3.js SVG Performance with 365+ Day Cells **What goes wrong:** Rendering 365 individual `` elements in SVG is fine. But adding tooltips, hover effects, and click handlers to each cell via d3's `.on()` creates 365+ event listeners. Combined with CSS transitions, this can cause jank on lower-end machines or when the widget shares the dashboard with other widgets. **Why it happens:** The naive approach of binding events per-element is the d3 tutorial default. **Prevention:** - Use event delegation: attach a single mousemove/click listener to the SVG container, use `document.elementFromPoint()` or d3's `pointer()` + geometric lookup to identify the hovered cell - Alternatively, for 365 cells, individual listeners are actually fine in modern browsers -- only becomes a real problem at 1000+ elements. So: measure first, optimize only if dashboard feels sluggish - Avoid CSS `transition` on all 365 rects simultaneously (e.g., on theme change) **Detection:** Dashboard sluggishness. DevTools Performance tab shows long "Recalculate Style" times. **Confidence:** HIGH (well-understood SVG performance characteristics) **Phase relevance:** Phase 2 (visualization) -- implement event delegation from the start if supporting multi-year views, otherwise defer optimization. ### Pitfall 7: Kimai's Webpack Encore Integration for Plugins **What goes wrong:** Kimai plugins must integrate with Kimai's existing Webpack Encore setup, not ship their own. Developers create a standalone webpack.config.js, build separately, and the assets don't load because Kimai's asset manifest doesn't know about them. **Why it happens:** Symfony Encore documentation describes standalone setup. Kimai's plugin asset pipeline is plugin-specific and less documented. **Prevention:** - Follow Kimai's plugin asset conventions: assets go in `Resources/public/` or use `assets/` with an `encore` entry - Study how existing Kimai plugins (e.g., CustomCSS, ExpensesBundle) register their JS/CSS - Kimai 2.x may use its own asset inclusion mechanism via Twig blocks in widget templates rather than Encore entries -- verify against the version you target - For d3.js, consider whether you even need Encore: a single pre-built JS file in `Resources/public/` that the widget template includes via `