{"version":3,"file":"sidebar-todo-panel.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/sidebar-todo-panel.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,SAAS,EAAiC,MAAM,wBAAwB,CAAC;AAOvF,4BAA4B;AAC5B,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,yBAAyB;AACzB,MAAM,WAAW,QAAQ;IACxB,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC/B,qBAAqB;IACrB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,2BAA2B;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,qCAAqC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,kCAAkC;AAClC,MAAM,WAAW,sBAAsB;IACtC,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,0DAA0D;IAC1D,eAAe,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAC;CACtB;AAiBD;;;;GAIG;AACH,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,aAAa,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAC3E,eAAe,GAAG,SAAS,CAc7B;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,aAAa,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GACxF,eAAe,GAAG,SAAS,CAc7B;AA0CD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,gBAAiB,YAAW,SAAS;IACjD,OAAO,CAAC,IAAI,CAA8B;IAC1C,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,WAAW,CAAoC;IAEvD,YAAY,MAAM,GAAE,OAAO,CAAC,sBAAsB,CAAM,EAEvD;IAED;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAGlC;IAED;;OAEG;IACH,KAAK,IAAI,IAAI,CAGZ;IAED;;OAEG;IACH,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,sBAAsB,CAAC,GAAG,IAAI,CAYvD;IAED,UAAU,IAAI,IAAI,CAEjB;IAED,OAAO,CAAC,eAAe;IAIvB;;OAEG;IACH,OAAO,CAAC,cAAc;IA0BtB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA2F9B;IAED;;OAEG;IACH,OAAO,CAAC,iBAAiB;CAczB;AAED,eAAe,gBAAgB,CAAC","sourcesContent":["/**\n * Sidebar Todo Panel\n *\n * Compact todo panel optimized for sidebar display.\n * Real-data-friendly interface that InteractiveMode can wire later.\n */\n\nimport { type Component, truncateToWidth, visibleWidth } from \"@apholdings/jensen-tui\";\nimport { theme } from \"../theme/theme.js\";\n\n// ============================================================================\n// Types & Interfaces\n// ============================================================================\n\n/** Status of a todo item */\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\n/** A single todo item */\nexport interface TodoItem {\n\t/** Unique identifier */\n\tid: string;\n\t/** Todo content/description */\n\tcontent: string;\n\t/** Active form description (what's currently being done) */\n\tactiveForm?: string;\n\t/** Current status */\n\tstatus: TodoStatus;\n\t/** Priority level (optional, higher = more important) */\n\tpriority?: number;\n\t/** Whether this item is persisted */\n\tisPersisted?: boolean;\n}\n\n/** Todo data for the panel */\nexport interface SidebarTodoData {\n\t/** All todo items */\n\titems: TodoItem[];\n\t/** Current in-progress item (optional, for quick access) */\n\tcurrent?: TodoItem;\n\t/** Total count of items */\n\ttotalCount: number;\n\t/** Count of completed items */\n\tcompletedCount: number;\n\t/** Whether todo list is persisted */\n\tisPersisted?: boolean;\n}\n\n/** Configuration for the panel */\nexport interface SidebarTodoPanelConfig {\n\t/** Header label shown in the panel (default: \"Todos\") */\n\ttitle: string;\n\t/** Minimum width for showing active form (default: 22) */\n\tactiveThreshold: number;\n\t/** Minimum width for showing all items (default: 26) */\n\titemsThreshold: number;\n\t/** Maximum items to show (default: 8) */\n\tmaxItems: number;\n\t/** Show priority indicators (default: false) */\n\tshowPriority: boolean;\n}\n\n// ============================================================================\n// Defaults\n// ============================================================================\n\nconst DEFAULT_CONFIG: SidebarTodoPanelConfig = {\n\ttitle: \"Todos\",\n\t// Show active form at narrower widths (18 instead of 22) for better visibility\n\tactiveThreshold: 18,\n\t// Show items at narrower widths (22 instead of 26) now that tools panel is removed\n\titemsThreshold: 22,\n\t// Keep maxItems at 8 for compact sidebar display\n\tmaxItems: 8,\n\tshowPriority: false,\n};\n\n/**\n * Build SidebarTodoData from the session's raw todo list shape\n * (`{ content, activeForm, status }`). Returns `undefined` when the list\n * is empty so callers can clear the panel instead of rendering it.\n */\nexport function buildSidebarTodoData(\n\ttodos: ReadonlyArray<{ content: string; activeForm: string; status: string }>,\n): SidebarTodoData | undefined {\n\tif (todos.length === 0) return undefined;\n\tconst items: TodoItem[] = todos.map((todo, index) => ({\n\t\tid: `todo-${index}`,\n\t\tcontent: todo.content,\n\t\tactiveForm: todo.activeForm,\n\t\tstatus: todo.status as TodoStatus,\n\t}));\n\treturn {\n\t\titems,\n\t\ttotalCount: items.length,\n\t\tcompletedCount: items.filter((item) => item.status === \"completed\").length,\n\t\tcurrent: items.find((item) => item.status === \"in_progress\"),\n\t};\n}\n\n/**\n * Build SidebarTodoData from the session's structured Task list shape\n * (`{ id, subject, activeForm?, status }`). Tasks are distinct from todos:\n * they are persistent, model-visible work items with an explicit id and\n * subject. This mapper reuses the shared panel rendering so the Task panel\n * renders with the same visual treatment as the Todo panel.\n */\nexport function buildSidebarTaskData(\n\ttasks: ReadonlyArray<{ id: string; subject: string; activeForm?: string; status: string }>,\n): SidebarTodoData | undefined {\n\tif (tasks.length === 0) return undefined;\n\tconst items: TodoItem[] = tasks.map((task) => ({\n\t\tid: task.id,\n\t\tcontent: task.subject,\n\t\tactiveForm: task.activeForm,\n\t\tstatus: task.status as TodoStatus,\n\t}));\n\treturn {\n\t\titems,\n\t\ttotalCount: items.length,\n\t\tcompletedCount: items.filter((item) => item.status === \"completed\").length,\n\t\tcurrent: items.find((item) => item.status === \"in_progress\"),\n\t};\n}\n\n// ============================================================================\n// Status Icons & Colors\n// ============================================================================\n\n/** Get icon for todo status - scannable checkbox format */\nfunction getStatusIcon(status: TodoStatus): string {\n\tswitch (status) {\n\t\tcase \"pending\":\n\t\t\treturn theme.fg(\"muted\", \"[ ]\");\n\t\tcase \"in_progress\":\n\t\t\treturn theme.fg(\"warning\", \"[-]\");\n\t\tcase \"completed\":\n\t\t\treturn theme.fg(\"success\", \"[x]\");\n\t}\n}\n\n/** Get color for todo status */\nfunction getStatusColorFn(status: TodoStatus): (text: string) => string {\n\tswitch (status) {\n\t\tcase \"pending\":\n\t\t\treturn (text: string) => theme.fg(\"text\", text);\n\t\tcase \"in_progress\":\n\t\t\treturn (text: string) => theme.fg(\"warning\", theme.bold(text));\n\t\tcase \"completed\":\n\t\t\treturn (text: string) => theme.fg(\"dim\", text);\n\t}\n}\n\n/** Get priority indicator */\nfunction getPriorityIndicator(priority?: number): string {\n\tif (!priority || priority <= 0) return \"\";\n\tif (priority >= 3) return theme.fg(\"error\", \"!\");\n\tif (priority >= 2) return theme.fg(\"warning\", \"~\");\n\treturn theme.fg(\"muted\", \"·\");\n}\n\n// ============================================================================\n// Panel Component\n// ============================================================================\n\n/**\n * Compact sidebar panel for todo status.\n *\n * Features:\n * - Shows todo progress and current task\n * - Width-aware rendering (compact at narrow widths)\n * - Real-data-friendly interface that InteractiveMode can wire later\n *\n * Usage:\n * ```typescript\n * const panel = new SidebarTodoPanel();\n *\n * // Update with data from InteractiveMode\n * panel.update({\n *   items: [\n *     { id: \"1\", content: \"Implement feature X\", status: \"completed\" },\n *     { id: \"2\", content: \"Write tests\", activeForm: \"Writing test cases\", status: \"in_progress\" },\n *     { id: \"3\", content: \"Review code\", status: \"pending\" },\n *   ],\n *   current: { id: \"2\", content: \"Write tests\", status: \"in_progress\" },\n *   totalCount: 3,\n *   completedCount: 1,\n * });\n *\n * // Render\n * const lines = panel.render(35);\n * ```\n */\nexport class SidebarTodoPanel implements Component {\n\tprivate data: SidebarTodoData | undefined;\n\tprivate config: SidebarTodoPanelConfig;\n\tprivate cachedLines: Map<number, string[]> = new Map();\n\n\tconstructor(config: Partial<SidebarTodoPanelConfig> = {}) {\n\t\tthis.config = { ...DEFAULT_CONFIG, ...config };\n\t}\n\n\t/**\n\t * Update the panel with new data\n\t */\n\tupdate(data: SidebarTodoData): void {\n\t\tthis.data = data;\n\t\tthis.invalidateCache();\n\t}\n\n\t/**\n\t * Clear the panel data\n\t */\n\tclear(): void {\n\t\tthis.data = undefined;\n\t\tthis.invalidateCache();\n\t}\n\n\t/**\n\t * Update configuration\n\t */\n\tconfigure(config: Partial<SidebarTodoPanelConfig>): void {\n\t\tconst changed =\n\t\t\tconfig.activeThreshold !== this.config.activeThreshold ||\n\t\t\tconfig.itemsThreshold !== this.config.itemsThreshold ||\n\t\t\tconfig.maxItems !== this.config.maxItems ||\n\t\t\tconfig.showPriority !== this.config.showPriority;\n\n\t\tthis.config = { ...this.config, ...config };\n\n\t\tif (changed) {\n\t\t\tthis.invalidateCache();\n\t\t}\n\t}\n\n\tinvalidate(): void {\n\t\tthis.invalidateCache();\n\t}\n\n\tprivate invalidateCache(): void {\n\t\tthis.cachedLines.clear();\n\t}\n\n\t/**\n\t * Render a single todo item line\n\t */\n\tprivate renderTodoLine(item: TodoItem, width: number, showActiveForm: boolean): string {\n\t\tconst icon = getStatusIcon(item.status);\n\t\tconst colorFn = getStatusColorFn(item.status);\n\t\tconst priority = this.config.showPriority ? getPriorityIndicator(item.priority) : \"\";\n\n\t\t// Build the content text\n\t\tlet contentText: string;\n\t\tif (item.status === \"in_progress\" && item.activeForm && showActiveForm) {\n\t\t\tcontentText = item.activeForm;\n\t\t} else {\n\t\t\tcontentText = item.content;\n\t\t}\n\n\t\t// Truncate to available width (account for 4-char icon + spaces)\n\t\tconst availableWidth = width - 6;\n\t\tconst truncated = truncateToWidth(contentText, availableWidth);\n\n\t\t// Build line\n\t\tconst parts: string[] = [icon, colorFn(truncated)];\n\t\tif (priority) {\n\t\t\tparts.push(priority);\n\t\t}\n\n\t\treturn `  ${parts.join(\" \")}`;\n\t}\n\n\trender(width: number): string[] {\n\t\t// Minimum width check\n\t\tif (width < 16) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Check cache\n\t\tconst cached = this.cachedLines.get(width);\n\t\tif (cached) {\n\t\t\treturn cached;\n\t\t}\n\n\t\tconst lines: string[] = [];\n\n\t\t// No data\n\t\tif (!this.data || this.data.items.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst { items, totalCount, completedCount } = this.data;\n\t\tconst showActiveForm = width >= this.config.activeThreshold;\n\t\tconst showItems = width >= this.config.itemsThreshold;\n\n\t\t// Header with progress\n\t\tconst progress = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;\n\t\tconst progressBar = this.renderProgressBar(progress, 6);\n\t\tconst headerParts: string[] = [\n\t\t\ttheme.bold(theme.fg(\"accent\", this.config.title)),\n\t\t\ttheme.fg(\"muted\", progressBar),\n\t\t\ttheme.fg(\"muted\", `${completedCount}/${totalCount}`),\n\t\t];\n\n\t\tlines.push(truncateToWidth(headerParts.join(\" \"), width));\n\n\t\t// Show items if width permits\n\t\tif (showItems) {\n\t\t\t// Sort: in_progress first, then pending, then completed\n\t\t\tconst sortedItems = [...items].sort((a, b) => {\n\t\t\t\tconst statusOrder: Record<TodoStatus, number> = {\n\t\t\t\t\tin_progress: 0,\n\t\t\t\t\tpending: 1,\n\t\t\t\t\tcompleted: 2,\n\t\t\t\t};\n\t\t\t\tconst statusDiff = statusOrder[a.status] - statusOrder[b.status];\n\t\t\t\tif (statusDiff !== 0) return statusDiff;\n\n\t\t\t\t// Within same status, sort by priority (higher first)\n\t\t\t\tif (this.config.showPriority) {\n\t\t\t\t\treturn (b.priority ?? 0) - (a.priority ?? 0);\n\t\t\t\t}\n\n\t\t\t\treturn 0;\n\t\t\t});\n\n\t\t\tconst visibleItems = sortedItems.slice(0, this.config.maxItems);\n\t\t\tfor (const item of visibleItems) {\n\t\t\t\tconst line = this.renderTodoLine(item, width, showActiveForm);\n\t\t\t\tlines.push(truncateToWidth(line, width));\n\t\t\t}\n\n\t\t\t// Overflow indicator\n\t\t\tif (items.length > this.config.maxItems) {\n\t\t\t\tconst remaining = items.length - this.config.maxItems;\n\t\t\t\tlines.push(truncateToWidth(theme.fg(\"muted\", `  +${remaining} more...`), width));\n\t\t\t}\n\t\t} else {\n\t\t\t// Compact mode: just show current task if any\n\t\t\tconst currentItem = items.find((item) => item.status === \"in_progress\");\n\t\t\tif (currentItem) {\n\t\t\t\tconst line = this.renderTodoLine(currentItem, width, false);\n\t\t\t\tlines.push(truncateToWidth(line, width));\n\t\t\t}\n\t\t}\n\n\t\t// Verify width constraints\n\t\tconst result = lines.map((line) => {\n\t\t\tconst lineWidth = visibleWidth(line);\n\t\t\tif (lineWidth > width) {\n\t\t\t\treturn truncateToWidth(line, width, \"\", false);\n\t\t\t}\n\t\t\treturn line;\n\t\t});\n\n\t\tconst spacedResult = result.length > 0 ? [\"\", ...result] : result;\n\n\t\t// Cache result\n\t\tif (spacedResult.length > 0) {\n\t\t\tthis.cachedLines.set(width, spacedResult);\n\t\t}\n\n\t\treturn spacedResult;\n\t}\n\n\t/**\n\t * Render a simple progress bar\n\t */\n\tprivate renderProgressBar(percent: number, barWidth: number): string {\n\t\tconst filled = Math.round((percent / 100) * barWidth);\n\t\tconst empty = barWidth - filled;\n\n\t\tif (filled === barWidth) {\n\t\t\treturn theme.fg(\"success\", \"█\".repeat(barWidth));\n\t\t}\n\n\t\tif (filled === 0) {\n\t\t\treturn theme.fg(\"dim\", \"░\".repeat(barWidth));\n\t\t}\n\n\t\treturn theme.fg(\"success\", \"█\".repeat(filled)) + theme.fg(\"dim\", \"░\".repeat(empty));\n\t}\n}\n\nexport default SidebarTodoPanel;\n"]}