--- 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