kimai-plugin-heatmap/.planning/research/PITFALLS.md

178 lines
15 KiB
Markdown

# 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 `<rect>` 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 `<script>` may be simpler and more maintainable than wiring into Encore
**Detection:** JavaScript console shows 404 for your JS file. Widget renders but heatmap area is blank.
**Confidence:** MEDIUM (Kimai's asset pipeline has evolved; verify against current version)
**Phase relevance:** Phase 1 (scaffold) -- resolve asset loading before writing d3 code.
## Minor Pitfalls
### Pitfall 8: Color Scale Not Adapting to Kimai Themes
**What goes wrong:** Hard-coding heatmap colors (e.g., GitHub's green palette) that clash with Kimai's dark mode or custom themes. The heatmap looks fine in default theme but illegible in dark mode.
**Why it happens:** GitHub's green heatmap palette is the default in every tutorial. Kimai supports theme switching.
**Prevention:**
- Read Kimai's CSS custom properties / SCSS variables for primary/accent colors
- Build the color scale from CSS variables: `getComputedStyle(document.documentElement).getPropertyValue('--primary-color')`
- Use luminance-based interpolation (light background to saturated primary) rather than a fixed green palette
- Test in both light and dark Kimai themes
**Detection:** Visual review in dark mode -- colors wash out or become invisible.
**Confidence:** HIGH (standard theming concern)
**Phase relevance:** Phase 2 (visualization) -- implement theme-aware colors from the start.
### Pitfall 9: Missing Empty State Handling
**What goes wrong:** New users or users with sparse data see a completely blank widget. No indication that the widget is working -- looks broken.
**Why it happens:** Developers test with seeded data and never encounter the empty state.
**Prevention:**
- Show an empty state message: "No time entries in this period"
- Handle partial data gracefully: a year view where only 2 months have data should still render the full year grid with empty (but visible) cells
- Include the empty state in test fixtures
**Detection:** Install plugin on a fresh Kimai instance with no time entries.
**Confidence:** HIGH (universal UX concern)
**Phase relevance:** Phase 2 (visualization) -- implement alongside the heatmap.
### Pitfall 10: Clicking Day Cell -- Kimai URL Structure Assumptions
**What goes wrong:** Building the "click to navigate to timesheet" URL by guessing Kimai's route structure (`/en/timesheet?date=2026-04-08`). The URL structure depends on Kimai's locale prefix, routing configuration, and filter parameter names, which change between versions.
**Why it happens:** Hardcoding URLs instead of using Symfony's router to generate them.
**Prevention:**
- Generate the timesheet URL on the PHP side using Symfony's `UrlGeneratorInterface` / `router->generate()`
- Pass the URL template to JavaScript as a data attribute: `data-timesheet-url-template="/en/timesheet/?daterange={date}~{date}"`
- Let JS only do string interpolation on the template, never URL construction
**Detection:** Clicking a cell leads to 404 or wrong page. Breaks when Kimai locale changes.
**Confidence:** MEDIUM (URL parameter names need verification against current Kimai version)
**Phase relevance:** Phase 2/3 (interactivity) -- when implementing click-to-navigate.
### Pitfall 11: Testing d3.js Output in a Headless Environment
**What goes wrong:** PHPUnit cannot test JavaScript. Jest/Vitest cannot access a real Kimai DOM. Developers skip frontend tests entirely because "it's just a visualization."
**Why it happens:** d3.js renders SVG in the DOM. Testing SVG output requires either a DOM environment (jsdom) or snapshot testing. Neither feels natural.
**Prevention:**
- Use jsdom with Vitest to test d3 rendering: create a container, run your heatmap function, assert SVG structure (number of rects, correct date attributes, color classes)
- Separate data transformation (pure functions: input dates/hours, output cell data) from rendering (d3 DOM manipulation). Test transformations with plain unit tests -- these catch the important bugs
- Snapshot test the SVG output for regression detection, but keep snapshots small (test one month, not a full year)
**Detection:** Heatmap breaks after refactoring with no test catching it.
**Confidence:** HIGH (standard d3 testing approach)
**Phase relevance:** Phase 1 (TDD setup) -- establish the test infrastructure before writing d3 code.
## Phase-Specific Warnings
| Phase Topic | Likely Pitfall | Mitigation |
|-------------|---------------|------------|
| Dev environment (Phase 0) | Nix+PHP+Kimai setup eats days (Pitfall 5) | Timebox to 1 day. Fall back to Docker if stuck. |
| Scaffold (Phase 1) | Widget not appearing on dashboard (Pitfall 3) | Study existing plugins first. Get empty widget visible before anything else. |
| Scaffold (Phase 1) | Asset loading fails (Pitfall 7) | Start with inline `<script>` in Twig, migrate to proper assets after it works. |
| Data layer (Phase 1) | Timezone-wrong day aggregation (Pitfall 2) | Aggregate in PHP, send date strings. Write test comparing against Kimai's own report. |
| Visualization (Phase 2) | Hard-coded colors break in dark mode (Pitfall 8) | Use CSS variables from day one. |
| Visualization (Phase 2) | Full d3 import bloats bundle (Pitfall 4) | Import specific modules only. |
| Interactivity (Phase 2-3) | Hardcoded timesheet URLs break (Pitfall 10) | Generate URL template server-side. |
| Maintenance (ongoing) | Kimai update breaks plugin (Pitfall 1) | Pin version, integration test that boots kernel. |
## Sources
- Kimai documentation at kimai.org/documentation (not fetched -- web tools unavailable)
- Kimai GitHub repository structure and plugin examples (based on training data, not live verification)
- d3.js modular import documentation (d3js.org)
- General Symfony bundle development practices
- Nix PHP packaging ecosystem knowledge
**Confidence note:** All findings are based on training data (cutoff ~early 2025). Kimai's plugin API, asset pipeline, and widget system should be verified against the current Kimai release before implementation begins. The Kimai ecosystem moves fast and is primarily one-developer-driven, so APIs can shift without extended deprecation periods.