# 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\` 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 | 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 | ## 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\` (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)