+
Heatmap will render here (Phase 3)
+
+ {% endblock %}
+{% endembed %}
+```
+
+### Routes Configuration
+```yaml
+# Resources/config/routes.yaml
+heatmap_controllers:
+ resource: '../../Controller/'
+ type: attribute
+```
+
+### PHPUnit Test (Unit, mocked repository)
+```php
+namespace KimaiPlugin\KimaiHeatmapBundle\Tests\Service;
+
+use App\Entity\User;
+use App\Repository\TimesheetRepository;
+use Doctrine\ORM\AbstractQuery;
+use Doctrine\ORM\QueryBuilder;
+use KimaiPlugin\KimaiHeatmapBundle\Service\HeatmapService;
+use PHPUnit\Framework\TestCase;
+
+class HeatmapServiceTest extends TestCase
+{
+ public function testGetDailyAggregation(): void
+ {
+ $mockResults = [
+ ['date' => '2026-04-01', 'duration' => 7200, 'count' => 3],
+ ['date' => '2026-04-02', 'duration' => 3600, 'count' => 1],
+ ];
+
+ $query = $this->createMock(AbstractQuery::class);
+ $query->method('getResult')->willReturn($mockResults);
+
+ $qb = $this->createMock(QueryBuilder::class);
+ $qb->method('select')->willReturnSelf();
+ $qb->method('addSelect')->willReturnSelf();
+ $qb->method('andWhere')->willReturnSelf();
+ $qb->method('setParameter')->willReturnSelf();
+ $qb->method('groupBy')->willReturnSelf();
+ $qb->method('orderBy')->willReturnSelf();
+ $qb->method('expr')->willReturn(new \Doctrine\ORM\Query\Expr());
+ $qb->method('getQuery')->willReturn($query);
+
+ $repo = $this->createMock(TimesheetRepository::class);
+ $repo->method('createQueryBuilder')->willReturn($qb);
+
+ $service = new HeatmapService($repo);
+ $user = $this->createMock(User::class);
+ $begin = new \DateTimeImmutable('2026-04-01');
+ $end = new \DateTimeImmutable('2026-04-30');
+
+ $result = $service->getDailyAggregation($user, $begin, $end);
+
+ $this->assertCount(2, $result);
+ $this->assertEquals('2026-04-01', $result[0]['date']);
+ $this->assertEquals(2.0, $result[0]['hours']);
+ $this->assertEquals(3, $result[0]['count']);
+ }
+}
+```
+
+## Validation Architecture
+
+### Test Framework
+| Property | Value |
+|----------|-------|
+| Framework | PHPUnit 10.x (Kimai's bundled version) |
+| Config file | `phpunit.xml` in plugin root (NEW -- Wave 0) |
+| Quick run command | `php vendor/bin/phpunit --configuration Tests/phpunit.xml` |
+| Full suite command | Same (small test count at this phase) |
+
+### Phase Requirements -> Test Map
+| Req ID | Behavior | Test Type | Automated Command | File Exists? |
+|--------|----------|-----------|-------------------|-------------|
+| TEST-01 | HeatmapService aggregates daily data correctly (duration, count, timezone grouping) | unit | `php vendor/bin/phpunit Tests/Service/HeatmapServiceTest.php` | Wave 0 |
+| TEST-02 | API endpoint returns correct JSON format, requires auth | unit | `php vendor/bin/phpunit Tests/Controller/HeatmapControllerTest.php` | Wave 0 |
+| PLUG-02 | Widget registers and renders on dashboard | manual | Boot Kimai, check dashboard | manual-only: requires running Kimai instance |
+
+### Wave 0 Gaps
+- [ ] `Tests/phpunit.xml` -- PHPUnit config for plugin standalone tests
+- [ ] `Tests/Service/HeatmapServiceTest.php` -- covers TEST-01
+- [ ] `Tests/Controller/HeatmapControllerTest.php` -- covers TEST-02
+- [ ] PHPUnit autoload bootstrap pointing to Kimai's vendor autoloader
+
+**Note on test bootstrapping:** The plugin lives inside Kimai's `var/plugins/` directory (symlinked). Tests need access to Kimai's autoloader. The `phpunit.xml` should set `bootstrap` to the Kimai `vendor/autoload.php`. For unit tests with mocked dependencies, this is sufficient. Full kernel-boot integration tests would need Kimai's test bootstrap, which is more complex and not needed for Phase 2.
+
+### Sampling Rate
+- **Per task commit:** `php dev/kimai/vendor/bin/phpunit --configuration Tests/phpunit.xml`
+- **Per wave merge:** Same
+- **Phase gate:** All PHPUnit tests green
+
+## Security Domain
+
+### Applicable ASVS Categories
+
+| ASVS Category | Applies | Standard Control |
+|---------------|---------|-----------------|
+| V2 Authentication | yes | Symfony `#[IsGranted('IS_AUTHENTICATED_REMEMBERED')]` -- same as Kimai core |
+| V3 Session Management | no | Handled by Kimai core |
+| V4 Access Control | yes | `#[IsGranted('view_own_timesheet')]` permission check -- same as Kimai widgets |
+| V5 Input Validation | yes | Date parameters validated/typed, no raw user input in queries |
+| V6 Cryptography | no | No crypto operations |
+
+### Known Threat Patterns
+
+| Pattern | STRIDE | Standard Mitigation |
+|---------|--------|---------------------|
+| SQL injection via date params | Tampering | Doctrine parameterized queries (`:begin`, `:end`) -- same as Kimai core |
+| Accessing other users' data | Information Disclosure | Query always filters by `$this->getUser()` -- never accept user ID from request |
+| CSRF on data endpoint | Tampering | GET-only endpoint, read-only data |
+
+## Assumptions Log
+
+| # | Claim | Section | Risk if Wrong |
+|---|-------|---------|---------------|
+| A1 | `@KimaiHeatmapBundle/` Twig namespace resolves `Resources/views/` in the plugin bundle | Architecture Patterns | Template not found -- would need to register path manually |
+| A2 | `autoconfigure: true` in plugin's `services.yaml` causes `WidgetInterface` auto-tagging | Architecture Patterns | Widget not discovered -- would need explicit tag in services.yaml |
+| A3 | `@theme/embeds/card.html.twig` is available to plugin templates | Code Examples | Template inheritance fails -- would need different base template |
+| A4 | Doctrine DQL `DATE()` function works with SQLite (dev) | Data Aggregation | Query fails in dev -- would need raw SQL or different function |
+
+**A4 is notable:** Kimai uses MySQL/MariaDB in production, but the dev environment setup from Phase 1 may use SQLite. The `DATE()` DQL function's behavior on SQLite should be verified during implementation.
+
+## Open Questions
+
+1. **SQLite compatibility in dev**
+ - What we know: Kimai's production uses MySQL/MariaDB. Phase 1 dev environment may use SQLite.
+ - What's unclear: Whether Doctrine's `DATE()` DQL function works on SQLite, and whether the `date_tz` column type is correctly handled.
+ - Recommendation: Check the dev database type during implementation. If SQLite, test the query works. If not, may need to adjust or use MySQL in dev.
+
+2. **Test bootstrap path**
+ - What we know: Plugin lives in `var/plugins/KimaiHeatmapBundle` (symlinked from project root). Tests need Kimai's autoloader.
+ - What's unclear: Exact relative path from plugin test config to Kimai's vendor autoloader.
+ - Recommendation: Resolve during test setup. The path is `../../vendor/autoload.php` from the plugin root when symlinked into `var/plugins/`.
+
+## Sources
+
+### Primary (HIGH confidence)
+- Local Kimai 2.52.0 source at `/home/toph/code/toph/kimai-heatmap/dev/kimai/` -- ALL architecture claims verified against actual source code
+- Files examined:
+ - `src/Widget/WidgetInterface.php` -- interface definition, `#[AutoconfigureTag]`
+ - `src/Widget/Type/AbstractWidget.php` -- base class, template name convention, timezone handling
+ - `src/Widget/Type/AbstractWidgetType.php` -- extended base class
+ - `src/Widget/WidgetService.php` -- widget registry
+ - `src/Widget/Type/DailyWorkingTimeChart.php` -- reference widget using TimesheetRepository
+ - `src/DependencyInjection/Compiler/WidgetCompilerPass.php` -- auto-registration
+ - `src/Twig/Runtime/WidgetExtension.php` -- render_widget() implementation
+ - `src/Controller/DashboardController.php` -- dashboard rendering, DashboardEvent
+ - `src/Event/DashboardEvent.php` -- default widget list management
+ - `src/Entity/Timesheet.php` -- entity structure, `date_tz` column
+ - `src/Repository/TimesheetRepository.php` -- query patterns
+ - `src/Timesheet/TimesheetStatisticService.php` -- daily aggregation reference
+ - `src/Timesheet/DateTimeFactory.php` -- timezone-aware date creation
+ - `src/API/BaseApiController.php` -- API controller pattern (FOS Rest)
+ - `src/Plugin/PluginInterface.php`, `PluginManager.php`, `PluginMetadata.php` -- plugin system
+ - `src/Kernel.php` -- plugin loading, route auto-import
+ - `templates/dashboard/index.html.twig` -- dashboard rendering
+ - `templates/widget/` -- existing widget templates
+ - `phpunit.xml.dist` -- test configuration
+
+### Secondary (MEDIUM confidence)
+- Symfony Bundle template namespace conventions (standard Symfony 6.x behavior) [ASSUMED for A1, A3]
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH -- verified against local Kimai source and composer.json
+- Architecture: HIGH -- all widget, route, and DI patterns verified in source
+- Data layer: HIGH -- `date_tz` column and query patterns verified in entity and service code
+- Pitfalls: HIGH -- derived from actual source code behavior
+- Testing: MEDIUM -- test bootstrap path needs verification during implementation
+
+**Research date:** 2026-04-08
+**Valid until:** 2026-05-08 (stable -- Kimai 2.52.0 is a fixed target)