kimai-plugin-heatmap/.planning/phase-1/RESEARCH.md
Christopher Mühl 29b54a340f
docs(phase-1): research dev environment setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 11:01:16 +02:00

361 lines
19 KiB
Markdown

# Phase 1: Dev Environment - Research
**Researched:** 2026-04-08
**Domain:** Nix flake devshell, Kimai 2.x local instance, MariaDB, PHP 8.2+, data seeding
**Confidence:** HIGH
## Summary
Phase 1 is about getting a reproducible dev environment where `nix develop` gives you a working Kimai instance with test data ready for plugin development. The key findings are:
1. **Kimai requires MySQL/MariaDB** -- SQLite support was removed years ago. The Nix flake must provision a local MariaDB instance (11.4.x available in nixpkgs). This is the single biggest complexity driver for this phase.
2. **Kimai 2.52.0** is the current latest stable release (March 2025), requiring PHP 8.1-8.5 and Symfony 6.4. PHP 8.2.29 is already available in the system nixpkgs as `php82`.
3. **Kimai ships built-in fixtures** (`bin/console kimai:reset:dev` loads Doctrine fixtures that create ~100-1000 timesheet entries per user across 3 years of data). No need to hand-roll a seed script.
4. **Plugins live in `var/plugins/`** with a symlink from the project directory. The namespace must be `KimaiPlugin\<BundleName>` and the bundle class must implement `App\Plugin\PluginInterface`.
**Primary recommendation:** Use a shell script (`dev/start.sh`) invoked from the Nix devshell that starts MariaDB in a local data directory, runs Kimai install + fixture loading, symlinks the plugin, and starts the Symfony dev server. Use `process-compose` to manage MariaDB + web server lifecycle.
<phase_requirements>
## Phase Requirements
| ID | Description | Research Support |
|----|-------------|------------------|
| DEV-01 | Nix flake/devshell provides a running local Kimai instance | Nix flake with php82, mariadb, composer, nodejs, symfony-cli, process-compose; shell hook bootstraps Kimai |
| DEV-02 | Local Kimai has a seeded database with realistic time entry data | `bin/console kimai:reset:dev` loads built-in Doctrine fixtures with thousands of timesheet entries |
| DEV-03 | Plugin is loadable in the local Kimai instance for manual testing | Symlink project dir into Kimai's `var/plugins/KimaiHeatmapBundle`, clear cache |
</phase_requirements>
## Standard Stack
### Core (Nix Flake Packages)
| Package | Nixpkgs Attr | Version | Purpose |
|---------|-------------|---------|---------|
| PHP | `php82` | 8.2.29 | Kimai runtime (with extensions: gd, intl, mbstring, pdo_mysql, xml, xsl, zip, tokenizer) | [VERIFIED: nix search nixpkgs php82] |
| MariaDB | `mariadb` | 11.4.8 | Database server (Kimai dropped SQLite support) | [VERIFIED: nix search nixpkgs mariadb] |
| Composer | `php82Packages.composer` | 2.8.x | PHP dependency management | [VERIFIED: system composer --version] |
| Node.js | `nodejs` | 22.x | JS tooling for d3/esbuild (later phases) | [VERIFIED: system node --version] |
| npm | (bundled with nodejs) | 10.x | JS package manager | [VERIFIED: system npm --version] |
| symfony-cli | `symfony-cli` | 5.16.0 | Dev web server with PHP integration | [VERIFIED: nix search nixpkgs symfony-cli] |
| process-compose | `process-compose` | 1.78.0 | Process manager for MariaDB + web server | [VERIFIED: nix search nixpkgs process-compose] |
### Kimai
| Property | Value | Source |
|----------|-------|--------|
| Latest stable | 2.52.0 | [VERIFIED: github.com/kimai/kimai/releases] |
| PHP support | 8.1 - 8.5 | [VERIFIED: composer.json require] |
| Symfony version | 6.4 LTS | [VERIFIED: composer.json extra] |
| Database | MySQL 8.3+ or MariaDB 11.1+ | [CITED: kimai.org/documentation/developers.html] |
| SQLite | NOT supported (removed 2021) | [VERIFIED: kimai.org/en/blog/2021/sqlite-and-ftp-support-removed] |
### Required PHP Extensions
From Kimai's `composer.json` [VERIFIED]:
`ext-gd`, `ext-intl`, `ext-json`, `ext-mbstring`, `ext-pdo`, `ext-tokenizer`, `ext-xml`, `ext-xsl`, `ext-zip`
Plus `pdo_mysql` for MariaDB connectivity.
## Architecture Patterns
### Nix Flake Structure
```
flake.nix # PHP, MariaDB, Composer, Node, symfony-cli, process-compose
flake.lock
dev/
process-compose.yaml # MariaDB + symfony web server process definitions
setup.sh # One-time: clone Kimai, composer install, DB setup, fixtures, symlink plugin
seed.sh # Re-run fixtures without full setup
```
### Pattern 1: Local MariaDB with Data Directory in Project
**What:** MariaDB runs from a local data directory (`./dev/.mariadb-data/`) using `mysqld --datadir` without needing root or system-level MySQL installation. [ASSUMED]
**When to use:** Always for this project -- avoids polluting the system.
**Example:**
```bash
# Initialize MariaDB data directory
mysql_install_db --datadir=./dev/.mariadb-data --auth-root-authentication-method=normal
# Start MariaDB on a non-standard port (to avoid conflicts)
mysqld --datadir=./dev/.mariadb-data --socket=./dev/.mariadb.sock --port=3307 --skip-grant-tables
```
### Pattern 2: Kimai Clone as Git-Ignored Dependency
**What:** Clone Kimai into `./dev/kimai/` (git-ignored), run `composer install` there, then symlink the plugin directory into `./dev/kimai/var/plugins/KimaiHeatmapBundle`.
**Why:** Keeps the host application separate from plugin source. Kimai is a dependency, not part of the plugin repo.
```
dev/
kimai/ # .gitignore'd -- cloned Kimai instance
var/
plugins/
KimaiHeatmapBundle -> ../../../../ # symlink to project root
```
### Pattern 3: Plugin Symlink for Live Development
**What:** Symlink the project root (which IS the plugin bundle) into Kimai's `var/plugins/` directory. [CITED: kimai.org/documentation/plugins.html]
**Why:** Code changes in the plugin are immediately reflected. No copy/install step needed.
```bash
ln -sf "$(pwd)" ./dev/kimai/var/plugins/KimaiHeatmapBundle
```
**Critical:** The symlink target directory name MUST match the bundle class name (`KimaiHeatmapBundle`).
### Pattern 4: process-compose for Multi-Service Dev
**What:** `process-compose` manages MariaDB and Symfony dev server as a single dev stack.
**Why:** Both services need to run simultaneously. process-compose handles startup order, log aggregation, and clean shutdown. Better than manual terminal management.
```yaml
# dev/process-compose.yaml
version: "0.5"
processes:
mariadb:
command: mysqld --datadir=./dev/.mariadb-data --socket=./dev/.mariadb.sock --port=3307
readiness_probe:
exec:
command: mysqladmin --socket=./dev/.mariadb.sock ping
initial_delay_seconds: 2
period_seconds: 1
kimai:
command: symfony server:start --no-tls --dir=./dev/kimai
depends_on:
mariadb:
condition: process_healthy
```
### Anti-Patterns to Avoid
- **Using SQLite for dev:** Kimai dropped SQLite support. Migrations will fail. No workaround. [VERIFIED]
- **Installing Kimai globally or via Composer require:** Kimai is the host application, not a library. Clone it.
- **Trying to Nixify Composer dependencies:** Let Composer manage `vendor/` normally inside the Kimai clone. Nix provides the PHP binary and extensions only.
- **Running MariaDB as a system service:** Use a local data directory. No root, no systemd, no conflicts with existing databases.
## Don't Hand-Roll
| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| Test data seeding | Custom SQL insert scripts | `bin/console kimai:reset:dev` | Kimai ships Doctrine fixtures that create users, customers, projects, activities, and 100-1000+ timesheet entries per user spanning 3 years [VERIFIED: TimesheetFixtures.php] |
| PHP dev web server | nginx/Apache config | `symfony server:start` | Handles PHP-FPM, routing, and `.env` loading automatically [VERIFIED: nixpkgs symfony-cli 5.16.0] |
| Process management | Shell scripts with `&` and `trap` | `process-compose` | Handles startup ordering, health checks, log aggregation, clean shutdown [VERIFIED: nixpkgs process-compose 1.78.0] |
| MariaDB initialization | Manual SQL DDL | `mysql_install_db` + `kimai:install` | Standard tooling that handles system tables, migrations, and schema creation |
## Database Schema (for Seeding Context)
Key tables for timesheet data [VERIFIED: Kimai source Entity classes]:
| Table | Key Columns | Notes |
|-------|-------------|-------|
| `kimai2_timesheet` | `id`, `start_time`, `end_time`, `duration`, `timezone`, `date_tz`, `user` (FK), `project_id` (FK), `activity_id` (FK), `rate`, `billable`, `exported` | Duration in seconds |
| `kimai2_customers` | `id`, `name`, `country`, `currency`, `timezone`, `visible` | Required parent for projects |
| `kimai2_projects` | `id`, `name`, `customer_id` (FK), `visible`, `billable` | Required parent for activities |
| `kimai2_activities` | `id`, `name`, `project_id` (FK, nullable), `visible`, `billable` | Can be global (null project) or project-specific |
| `kimai2_users` | `id`, `username`, `email`, `roles`, `password`, `timezone` | Created via `kimai:user:create` |
**The `kimai:reset:dev` command** loads Doctrine fixtures that populate ALL these tables with realistic data, including:
- 5 users (clara_customer, john_user, tony_teamlead, anna_admin, susan_super) -- all with password "password" [CITED: kimai.org/documentation/developers.html]
- Multiple customers, projects, and activities
- 100-1000 timesheet entries per user spanning ~3 years back [VERIFIED: TimesheetFixtures.php source]
## Kimai Setup Commands (Exact Sequence)
```bash
# 1. Clone Kimai at specific version
git clone -b 2.52.0 --depth 1 https://github.com/kimai/kimai.git dev/kimai
# 2. Configure environment
cp dev/kimai/.env.dist dev/kimai/.env
# Edit .env: set DATABASE_URL, APP_SECRET
# 3. Install PHP dependencies (--no-dev is fine for plugin dev; add --dev if you need Kimai's own test tools)
cd dev/kimai && composer install --no-dev --optimize-autoloader
# 4. Create database and run migrations
bin/console kimai:install -n
# 5. Load demo fixtures (drops and recreates schema with test data!)
bin/console kimai:reset:dev
# 6. Symlink plugin
ln -sf "$(pwd)/../../" var/plugins/KimaiHeatmapBundle
# 7. Clear cache (required after adding plugin)
bin/console cache:clear
```
**DATABASE_URL format for local MariaDB:**
```
DATABASE_URL=mysql://root@127.0.0.1:3307/kimai?charset=utf8mb4&serverVersion=11.4.8-MariaDB
```
## Common Pitfalls
### Pitfall 1: MariaDB Socket vs TCP Connection
**What goes wrong:** Kimai tries to connect via Unix socket (default MySQL behavior) but the local MariaDB is listening on a custom socket path or TCP port.
**Why it happens:** Default MySQL client config looks for `/var/run/mysqld/mysqld.sock` which doesn't exist in a Nix devshell.
**How to avoid:** Use TCP connection (`127.0.0.1:3307`) in DATABASE_URL, not `localhost` (which triggers socket mode on Linux). Or set `--socket` path explicitly.
**Warning signs:** "Can't connect to local MySQL server through socket" errors.
### Pitfall 2: Kimai Cache Issues After Plugin Symlink
**What goes wrong:** After symlinking the plugin, Kimai doesn't see it or throws container compilation errors.
**Why it happens:** Symfony caches the container, routes, and bundle list. A new plugin requires cache clear.
**How to avoid:** Always run `bin/console cache:clear` after symlinking. In dev, set `APP_ENV=dev` to enable auto-recompilation.
**Warning signs:** Plugin not listed in admin, or DI errors referencing old cached container.
### Pitfall 3: PHP Extension Mismatch in Nix
**What goes wrong:** Kimai fails at runtime with "extension not found" errors even though you listed extensions in `flake.nix`.
**Why it happens:** Nix PHP needs explicit extension configuration via `php.withExtensions` or `php.buildEnv`. Simply adding `php82` doesn't include all extensions.
**How to avoid:** Build a custom PHP with required extensions:
```nix
php = pkgs.php82.buildEnv {
extensions = { enabled, all }: enabled ++ (with all; [ xsl ]);
};
```
The default `enabled` set includes most common extensions (gd, intl, mbstring, pdo, xml, zip, etc.) but `xsl` is often missing. [ASSUMED]
**Warning signs:** `composer install` fails, or Kimai throws "class not found" for Intl/GD operations.
### Pitfall 4: kimai:reset:dev Drops Everything
**What goes wrong:** Running `kimai:reset:dev` drops the entire database schema and reloads fixtures. Developers run it thinking it adds data on top of existing data.
**Why it happens:** The command runs `doctrine:fixtures:load` which purges all tables first.
**How to avoid:** Only run during initial setup or when you explicitly want a clean slate. For adding entries, write a separate seed script or use Kimai's API.
**Warning signs:** Custom data disappears after running the command.
### Pitfall 5: Plugin Directory Name Must Match Bundle Class
**What goes wrong:** Symlink named `heatmap-plugin` or `KimaiHeatmap` instead of `KimaiHeatmapBundle`. Kimai's plugin loader silently ignores it.
**Why it happens:** Kimai expects the directory name in `var/plugins/` to match the bundle class name exactly.
**How to avoid:** Name the symlink `KimaiHeatmapBundle` to match the class `KimaiHeatmapBundle.php`.
**Warning signs:** Plugin not appearing in Kimai admin, no errors in logs.
## Validation Architecture
### Test Framework
| Property | Value |
|----------|-------|
| Framework | PHPUnit (match Kimai's version) |
| Config file | None yet -- Wave 0 task |
| Quick run command | `php vendor/bin/phpunit --filter TestName` |
| Full suite command | `php vendor/bin/phpunit` |
### Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| DEV-01 | Nix devshell provides PHP, Composer, Node | smoke | `nix develop --command bash -c "php --version && composer --version && node --version"` | N/A (manual) |
| DEV-02 | Seeded database has timesheet entries | smoke | `cd dev/kimai && bin/console doctrine:query:sql "SELECT COUNT(*) FROM kimai2_timesheet"` | N/A (manual) |
| DEV-03 | Plugin is loadable | smoke | `cd dev/kimai && bin/console kimai:plugins` (should list the plugin) | N/A (manual) |
### Wave 0 Gaps
- [ ] None for this phase -- DEV-01/02/03 are infrastructure tasks verified by smoke commands, not unit tests.
## Environment Availability
| Dependency | Required By | Available | Version | Fallback |
|------------|------------|-----------|---------|----------|
| Nix | Flake/devshell | Yes | 2.93.3 (Lix) | -- |
| PHP 8.2 | Kimai runtime | Yes (system) | 8.2.29 | Provided by Nix flake |
| Composer | PHP deps | Yes (system) | 2.8.12 | Provided by Nix flake |
| Node.js | JS tooling | Yes (system) | 22.21.1 | Provided by Nix flake |
| MariaDB | Database | No (not installed) | -- | Provided by Nix flake (11.4.8 available) |
| symfony-cli | Dev server | No (not installed) | -- | Provided by Nix flake (5.16.0 available) |
| process-compose | Process mgmt | No (not installed) | -- | Provided by Nix flake (1.78.0 available) |
**Missing dependencies with no fallback:** None -- all provided by the Nix flake.
## Plugin Loading Mechanism
Kimai discovers plugins automatically from `var/plugins/`. Key details [CITED: kimai.org/documentation/plugins.html]:
1. Plugin directory must contain a bundle class that `implements PluginInterface`
2. Namespace must be `KimaiPlugin\<BundleName>` (the `KimaiPlugin` vendor prefix is mandatory)
3. Routes auto-load from `Resources/config/routes.{php,yaml}` inside the plugin directory
4. Plugins load in ALL environments without modifying `config/bundles.php`
5. The `composer.json` must have `"type": "kimai-plugin"` and an `extra.kimai` section with `require` (minimum Kimai version as integer: major*10000 + minor*100 + patch) and `name`
### Minimum Plugin composer.json
```json
{
"name": "kimai-plugin/heatmap-bundle",
"type": "kimai-plugin",
"description": "GitHub-style activity heatmap dashboard widget",
"license": "MIT",
"autoload": {
"psr-4": {
"KimaiPlugin\\KimaiHeatmapBundle\\": ""
}
},
"extra": {
"kimai": {
"require": 25200,
"name": "Activity Heatmap"
}
}
}
```
## Assumptions Log
| # | Claim | Section | Risk if Wrong |
|---|-------|---------|---------------|
| A1 | MariaDB can run with `--datadir` pointing to a local project directory without root | Architecture Patterns | HIGH -- would need alternative DB setup approach (Docker, system service) |
| A2 | Nix php82 default `enabled` extensions cover most of Kimai's requirements except `xsl` | Common Pitfalls | MEDIUM -- may need to explicitly list more extensions |
| A3 | `symfony server:start` works with the Nix-provided PHP binary | Architecture Patterns | LOW -- symfony-cli auto-detects PHP; fallback is `php -S` |
## Open Questions
1. **Exact `php.buildEnv` extension list needed**
- What we know: Kimai requires gd, intl, mbstring, pdo_mysql, xml, xsl, zip, tokenizer, json
- What's unclear: Which of these are in Nix's default `enabled` set vs. needing explicit addition
- Recommendation: Start with `enabled ++ [xsl pdo_mysql]` and iterate if `composer install` fails
2. **MariaDB version compatibility with Kimai migrations**
- What we know: Kimai docs say MariaDB 11.1+; nixpkgs has 11.4.8
- What's unclear: Whether any migration uses MySQL-specific syntax that breaks on MariaDB 11.4
- Recommendation: Use 11.4.8 (latest in nixpkgs); if issues arise, try 10.11.14 (`mariadb_1011`)
## Sources
### Primary (HIGH confidence)
- [GitHub kimai/kimai releases](https://github.com/kimai/kimai/releases) -- version 2.52.0 confirmed
- [Kimai composer.json](https://raw.githubusercontent.com/kimai/kimai/refs/heads/main/composer.json) -- PHP/Symfony versions, extensions
- [Kimai Entity/Timesheet.php](https://github.com/kimai/kimai/blob/main/src/Entity/Timesheet.php) -- database schema
- [Kimai Entity/Customer.php](https://github.com/kimai/kimai/blob/main/src/Entity/Customer.php) -- customer table schema
- [Kimai Entity/Project.php](https://github.com/kimai/kimai/blob/main/src/Entity/Project.php) -- project table schema
- [Kimai Entity/Activity.php](https://github.com/kimai/kimai/blob/main/src/Entity/Activity.php) -- activity table schema
- [Kimai WidgetInterface.php](https://github.com/kimai/kimai/blob/main/src/Widget/WidgetInterface.php) -- widget API
- [Kimai TimesheetFixtures.php](https://github.com/kimai/kimai/blob/main/src/DataFixtures/TimesheetFixtures.php) -- fixture data details
- [Kimai DemoBundle](https://github.com/kimai/DemoBundle) -- reference plugin implementation
### Secondary (MEDIUM confidence)
- [Kimai plugin documentation](https://www.kimai.org/documentation/plugins.html) -- plugin loading mechanism
- [Kimai installation docs](https://www.kimai.org/documentation/installation.html) -- setup commands
- [Kimai developer docs](https://www.kimai.org/documentation/developers.html) -- dev environment, fixture commands
- [Kimai SQLite removal announcement](https://www.kimai.org/en/blog/2021/sqlite-and-ftp-support-removed) -- database requirements
- [loophp/nix-shell](https://github.com/loophp/nix-shell) -- Nix PHP packaging reference
### Tertiary (LOW confidence)
- None
## Metadata
**Confidence breakdown:**
- Standard stack: HIGH -- versions verified against nixpkgs and Kimai source
- Architecture: HIGH for Kimai plugin mechanics (verified against source), MEDIUM for Nix MariaDB setup (assumed pattern)
- Pitfalls: HIGH -- based on verified Kimai constraints (no SQLite, cache clearing, fixture behavior)
**Research date:** 2026-04-08
**Valid until:** 2026-05-08 (Kimai releases roughly monthly; pin to 2.52.0)