diff --git a/CLAUDE.md b/CLAUDE.md index 3122654..f3e18c8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -86,6 +86,15 @@ src/development_hub/ | `Ctrl+]` | Expand all sections | | `Ctrl+[` | Collapse all sections | +### Dashboard Data Sync + +The dashboard automatically syncs data between documentation files: + +- **Deliverables → TODOs**: When loading a project dashboard, deliverables from `milestones.md` tables are synced to `todos.md`. If a deliverable doesn't exist as a todo (matched by text + milestone ID), it's added with the appropriate `@M#` tag and priority. +- **TODOs → Milestone Progress**: The milestone progress bar is calculated from linked todos (items tagged with `@M#`), not from the deliverables table. + +This means you can define major work items in milestone deliverable tables, and they'll automatically appear in your todo list for tracking. + ## CLI Scripts ### `bin/new-project` diff --git a/src/development_hub/project_list.py b/src/development_hub/project_list.py index d46a1fc..215127d 100644 --- a/src/development_hub/project_list.py +++ b/src/development_hub/project_list.py @@ -14,6 +14,7 @@ from PySide6.QtWidgets import ( QMenu, QMessageBox, QProgressBar, + QPushButton, QTextEdit, QVBoxLayout, QWidget, diff --git a/src/development_hub/views/dashboard.py b/src/development_hub/views/dashboard.py index a94f36f..ec32acf 100644 --- a/src/development_hub/views/dashboard.py +++ b/src/development_hub/views/dashboard.py @@ -88,6 +88,7 @@ from development_hub.services.git_service import GitService from development_hub.services.health_checker import HealthChecker from development_hub.models.health import HealthStatus from development_hub.parsers.todos_parser import TodosParser +from development_hub.models.todo import Todo, TodoList from development_hub.parsers.goals_parser import MilestonesParser, GoalsParser from development_hub.widgets.progress_bar import MilestoneProgressBar from development_hub.widgets.stat_card import StatCardRow @@ -1107,6 +1108,19 @@ class ProjectDashboard(QWidget): if todos_path.exists(): parser = TodosParser(todos_path) todo_list = parser.parse() + else: + # Create empty TodoList if file doesn't exist + todo_list = TodoList(source_file=str(todos_path)) + + # Sync deliverables from milestones to todos (adds missing items) + if self._milestones_list and todo_list: + synced = self._sync_deliverables_to_todos( + self._milestones_list, todo_list, todos_path + ) + if synced: + # Reload todo_list after sync to get fresh data + parser = TodosParser(todos_path) + todo_list = parser.parse() # Create MilestoneWidget for each milestone (keep original file order) first_active_shown = False @@ -3141,6 +3155,81 @@ generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} if changed: self._save_milestones() + def _sync_deliverables_to_todos( + self, milestones: list, todo_list, todos_path + ) -> bool: + """Sync milestone deliverables to todos. + + For each deliverable in a milestone, check if a matching todo exists. + If not, create a new todo with the appropriate milestone tag and status. + + Args: + milestones: List of Milestone objects + todo_list: TodoList to add deliverables to + todos_path: Path to todos.md for saving + + Returns: + True if any todos were added, False otherwise + """ + from development_hub.models.goal import DeliverableStatus + + if not milestones or not todo_list: + return False + + added_count = 0 + + for milestone in milestones: + if not milestone.deliverables: + continue + + # Get existing todos for this milestone + existing_todos = todo_list.get_by_milestone(milestone.id) + existing_texts = {t.text.lower().strip() for t in existing_todos} + + for deliverable in milestone.deliverables: + deliverable_text = deliverable.name.strip() + deliverable_text_lower = deliverable_text.lower() + + # Check if this deliverable already exists as a todo + if deliverable_text_lower in existing_texts: + continue + + # Map deliverable status to todo properties + if deliverable.status == DeliverableStatus.DONE: + completed = True + priority = "high" + else: + completed = False + # In Progress gets high priority, Not Started gets medium + if deliverable.status == DeliverableStatus.IN_PROGRESS: + priority = "high" + else: + priority = "medium" + + # Create new todo + new_todo = Todo( + text=deliverable_text, + completed=completed, + priority=priority, + milestone=milestone.id, + ) + + # Mark complete with today's date if done + if completed: + new_todo.mark_complete() + + todo_list.add_todo(new_todo) + added_count += 1 + + # Save if any todos were added + if added_count > 0 and todos_path: + parser = TodosParser(todos_path) + parser._frontmatter = parser.frontmatter # Preserve frontmatter + parser.save(todo_list) + return True + + return False + def _on_milestone_todo_toggled(self, todo, completed): """Handle todo toggle from within milestone widget.