kimai-plugin-heatmap/.planning/phases/06-renderer-architecture/06-UI-SPEC.md
2026-04-09 00:25:52 +02:00

150 lines
5.7 KiB
Markdown

---
phase: 6
slug: renderer-architecture
status: draft
shadcn_initialized: false
preset: none
created: 2026-04-09
---
# Phase 6 — UI Design Contract
> Visual and interaction contract for the renderer architecture refactor. This phase ships zero visual changes -- the contract documents the existing v1.0 visual baseline as a regression guard.
---
## Design System
| Property | Value |
|----------|-------|
| Tool | none |
| Preset | not applicable |
| Component library | Tabler (Kimai host app) |
| Icon library | not applicable (no icons in this phase) |
| Font | `var(--tblr-font-sans-serif)` (inherited from Kimai/Tabler) |
**Note:** This plugin renders inside a Kimai dashboard card (`@theme/embeds/card.html.twig`). All surface colors, fonts, and border styles inherit from Tabler CSS custom properties. No standalone design system is used or needed.
---
## Spacing Scale
Declared values from existing v1.0 CSS (must remain unchanged after refactor):
| Token | Value | Usage |
|-------|-------|-------|
| cell-gap | 2px | Gap between heatmap cells (SVG attribute, not CSS) |
| tooltip-pad | 6px 10px | Tooltip internal padding |
| wrapper-gap | 16px | Gap between heatmap SVG area and filter dropdown |
| stats-gap | 16px | Gap between stat items in footer row |
| stats-top | 12px | Padding above stats row |
| filter-top | 20px | Padding above filter dropdown (desktop) |
Exceptions: Cell size and margins are controlled by `HeatmapConfig` in TypeScript (not CSS). These values must transfer unchanged to `DEFAULT_CONFIG` in the refactored code.
---
## Typography
All typography inherits from Kimai/Tabler. The plugin declares only two overrides:
| Role | Size | Weight | Line Height | Source |
|------|------|--------|-------------|--------|
| SVG label | 10px | 400 (normal) | default | `.heatmap-label` in heatmap.css |
| Tooltip / Stats | 0.8125rem (13px) | 400 (normal) | default | `.heatmap-tooltip`, `.heatmap-stats` |
| Stat value | 0.8125rem (13px) | 600 (semibold) | default | `.heatmap-stats .stat-value` |
No new typography tokens in this phase.
---
## Color
All colors use Tabler CSS custom properties. The plugin does not declare its own palette.
| Role | Value | Usage |
|------|-------|-------|
| Body text | `var(--tblr-body-color)` | SVG labels, stat values, tooltip text |
| Surface | `var(--tblr-bg-surface)` | Tooltip background |
| Surface secondary | `var(--tblr-bg-surface-secondary)` | Empty heatmap cells |
| Border | `var(--tblr-border-color)` | Tooltip border |
| Secondary text | `var(--tblr-secondary, #6c757d)` | Stat labels |
| Heatmap scale | `['#9be9a8', '#40c463', '#30a14e', '#216e39']` | Cell fill intensity (4-step quantize) |
**Regression constraint:** The 4-color heatmap scale array must remain identical in `shared/color-scale.ts` after extraction. These are hardcoded GitHub-green values, not Tabler variables.
Accent reserved for: not applicable (no accent color in this phase).
---
## Copywriting Contract
Phase 6 introduces no new copy. Existing copy must survive the refactor unchanged:
| Element | Copy | Location after refactor |
|---------|------|------------------------|
| Tooltip: date line | `{formatted date}` | `shared/tooltip.ts` |
| Tooltip: hours line | `{N}h {M}m` | `shared/tooltip.ts` |
| Tooltip: entries line | `{N} entries` | `shared/tooltip.ts` |
| Stats: streak | `Current streak: {N} days` | `shared/stats.ts` |
| Stats: total | `Total: {N}h` | `shared/stats.ts` |
| Stats: average | `Daily avg: {N}h` | `shared/stats.ts` |
| Stats: busiest | `Busiest: {date}` | `shared/stats.ts` |
| Empty state | No explicit empty state (heatmap renders with all-empty cells) | `renderers/year.ts` |
| Error state | No explicit error state (console.error only) | `heatmap.ts` orchestrator |
| Primary CTA | not applicable (no user actions beyond hover/click) | -- |
| Destructive confirmation | not applicable (no destructive actions) | -- |
---
## Interaction Contract
No new interactions. Existing interactions must work identically after refactor:
| Interaction | Behavior | Must survive in |
|-------------|----------|-----------------|
| Cell hover | Show tooltip with date, hours, entry count | `shared/tooltip.ts` + renderer |
| Cell click | Navigate to Kimai timesheet filtered by date | `RenderContext.onCellClick` callback |
| Cell hover (weekend) | Same tooltip, slightly reduced opacity (0.65 vs 0.75) | `.heatmap-weekend:hover` CSS (unchanged) |
| Filter select change | Re-render heatmap with filtered project data | `heatmap.ts` orchestrator via `HeatmapState.filters` |
| Window resize | Re-render to fit container width | `heatmap.ts` orchestrator (resize listener) |
---
## Regression Checklist
These visual properties must be pixel-identical before and after the refactor:
- [ ] Cell border-radius: `rx=2 ry=2`
- [ ] Cell hover opacity: `0.75` (normal), `0.65` (weekend)
- [ ] Tooltip shadow: `0 2px 8px rgba(0,0,0,0.12)`
- [ ] Tooltip border-radius: `4px`
- [ ] Empty cell fill: `var(--tblr-bg-surface-secondary)`
- [ ] Heatmap color scale: 4 greens `#9be9a8 #40c463 #30a14e #216e39`
- [ ] Stats layout: flex row with `16px` gap, wrapping
- [ ] SVG month/day labels: `10px` sans-serif
- [ ] Filter dropdown position: right side on desktop, above on mobile (<1330px)
- [ ] Scroll behavior: horizontal scroll on `.heatmap-svg-area`
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| not applicable | none | not applicable |
No component registries or third-party blocks in this phase.
---
## Checker Sign-Off
- [ ] Dimension 1 Copywriting: PASS
- [ ] Dimension 2 Visuals: PASS
- [ ] Dimension 3 Color: PASS
- [ ] Dimension 4 Typography: PASS
- [ ] Dimension 5 Spacing: PASS
- [ ] Dimension 6 Registry Safety: PASS
**Approval:** pending