--- phase: 07-mode-switcher-week-mode plan: 01 type: execute wave: 1 depends_on: [] files_modified: - Resources/views/widget/heatmap.html.twig - assets/src/ui/controls.ts - assets/src/heatmap.ts - Resources/public/heatmap.css - assets/test/controls.test.ts autonomous: true requirements: [VIZ-01, VIZ-05, TEST-01] must_haves: truths: - "A segmented control in the widget header shows Year and Week buttons" - "Clicking Week sets state.mode to week and triggers doRender" - "A separate compact segmented control shows Hours and Count buttons" - "Clicking Count sets state.metric to count and triggers doRender" - "Controls persist across re-renders (live in card header, not SVG area)" - "Stats row is hidden when mode is week, shown when mode is year" artifacts: - path: "assets/src/ui/controls.ts" provides: "createModeControl and createMetricControl functions" exports: ["createModeControl", "createMetricControl"] - path: "Resources/views/widget/heatmap.html.twig" provides: "Controls placeholder div in card header" contains: "heatmap-controls" - path: "assets/test/controls.test.ts" provides: "Tests for mode switcher and metric toggle" min_lines: 40 key_links: - from: "assets/src/heatmap.ts" to: "assets/src/ui/controls.ts" via: "import createModeControl, createMetricControl" pattern: "import.*controls" - from: "assets/src/ui/controls.ts" to: "state.mode / state.metric" via: "onChange callbacks" pattern: "onChange" --- Build the mode switcher and hours/count toggle UI controls, wire them into the heatmap orchestrator, and add the Twig template placeholder. Purpose: Enables switching between year/week modes and hours/count metrics -- the UI backbone for all v1.1 visualization features. Output: Two Tabler nav-segmented controls in the widget header, wired to HeatmapState, with tests. @/home/toph/code/toph/kimai-heatmap/.claude/get-shit-done/workflows/execute-plan.md @/home/toph/code/toph/kimai-heatmap/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-mode-switcher-week-mode/07-CONTEXT.md @.planning/phases/07-mode-switcher-week-mode/07-RESEARCH.md @.planning/phases/07-mode-switcher-week-mode/07-UI-SPEC.md @.planning/phases/06-renderer-architecture/06-01-SUMMARY.md @.planning/phases/06-renderer-architecture/06-02-SUMMARY.md From assets/src/types.ts: ```typescript export type DisplayMetric = 'hours' | 'count'; export type HeatmapMode = 'year' | 'week' | 'day' | 'combined'; ``` From assets/src/state.ts: ```typescript export interface HeatmapState { mode: HeatmapMode; metric: DisplayMetric; filters: FilterState; weekStart: string; data: HeatmapData | null; } export function createInitialState(weekStart: string): HeatmapState; ``` From assets/src/renderers/registry.ts: ```typescript export function getRenderer(mode: string): ModeRenderer; ``` From assets/src/shared/stats.ts: ```typescript export function renderStats(container: HTMLElement, days: DayEntry[]): void; ``` From assets/src/heatmap.ts (current doRender pattern): ```typescript const doRender = () => { if (!state.data) return; const renderer = getRenderer(state.mode); renderer.destroy?.(); renderer.render({ container: svgArea, data: state.data, state, config: {...}, onCellClick, emptyMessage: ... }); renderStats(container, state.data.days); svgArea.scrollLeft = svgArea.scrollWidth; }; ``` Task 1: Create UI controls module and wire into heatmap orchestrator assets/src/ui/controls.ts, assets/src/heatmap.ts, Resources/views/widget/heatmap.html.twig, Resources/public/heatmap.css, assets/test/controls.test.ts assets/src/heatmap.ts, assets/src/types.ts, assets/src/state.ts, assets/src/shared/stats.ts, Resources/views/widget/heatmap.html.twig, Resources/public/heatmap.css, .planning/phases/07-mode-switcher-week-mode/07-UI-SPEC.md - createModeControl('year', [{key:'year',label:'Year'},{key:'week',label:'Week'}], cb) returns nav element with class 'nav nav-segmented' and role='tablist' - createModeControl: first button has class 'nav-link active', second has 'nav-link' (no active) - createModeControl: clicking inactive button adds 'active' to it, removes 'active' from previous, calls onChange with key - createMetricControl('hours', cb) returns nav element with class 'nav nav-segmented nav-sm' - createMetricControl: clicking 'Count' calls onChange with 'count' - Both controls have role='tab' on buttons and aria-selected attribute **1. Create `assets/src/ui/controls.ts`** with two exported functions: `createModeControl(activeMode: string, modes: Array<{key: string; label: string}>, onChange: (mode: string) => void): HTMLElement` - Create `