diff --git a/using-git-worktrees-to-run-parallel-ai-agents/todo-app/.gitignore b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/.gitignore new file mode 100644 index 0000000000..ffd07d855e --- /dev/null +++ b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/.gitignore @@ -0,0 +1,7 @@ +# .gitignore + +__pycache__/ +*.pyc +.venv/ +tasks.json +.claude/worktrees/ \ No newline at end of file diff --git a/using-git-worktrees-to-run-parallel-ai-agents/todo-app/pyproject.toml b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/pyproject.toml new file mode 100644 index 0000000000..2ec9b1caf9 --- /dev/null +++ b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/pyproject.toml @@ -0,0 +1,15 @@ +# pyproject.toml + +[project] +name = "todo-app" +version = "0.1.0" +description = "A minimal terminal to-do app for practicing Git worktrees" +requires-python = ">=3.10" +dependencies = ["textual>=8.0"] + +[project.scripts] +todo = "todo_app.app:main" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" \ No newline at end of file diff --git a/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/__init__.py b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/app.py b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/app.py new file mode 100644 index 0000000000..6b6fe50776 --- /dev/null +++ b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/app.py @@ -0,0 +1,78 @@ +# todo_app/app.py + +from textual.app import App, ComposeResult +from textual.widgets import Footer, Input, Label, ListItem, ListView + +from todo_app.storage import load_tasks, save_tasks + + +def build_item(task): + marker = "[x]" if task["done"] else "[ ]" + item = ListItem(Label(f"{marker} {task['text']}", markup=False)) + item.set_class(task["done"], "done") + return item + + +class TodoApp(App): + CSS_PATH = "app.tcss" + BINDINGS = [ + ("space", "toggle", "Toggle"), + ("d", "delete", "Delete"), + ("q", "quit", "Quit"), + ] + + def __init__(self): + super().__init__() + self.tasks = load_tasks() + + def compose(self) -> ComposeResult: + yield Label("todo", id="title") + yield Input(placeholder="Add a task, then press Enter", id="new-task") + yield ListView(*(build_item(task) for task in self.tasks), id="tasks") + yield Footer() + + def on_mount(self) -> None: + self.query_one("#tasks", ListView).index = 0 + self.query_one("#new-task", Input).focus() + + def on_input_submitted(self, event: Input.Submitted) -> None: + text = event.value.strip() + if not text: + return + task = {"text": text, "done": False} + self.tasks.append(task) + list_view = self.query_one("#tasks", ListView) + list_view.append(build_item(task)) + if list_view.index is None: + list_view.index = 0 + event.input.clear() + save_tasks(self.tasks) + + def action_toggle(self) -> None: + list_view = self.query_one("#tasks", ListView) + item = list_view.highlighted_child + if item is None: + return + task = self.tasks[list_view.index] + task["done"] = not task["done"] + marker = "[x]" if task["done"] else "[ ]" + item.query_one(Label).update(f"{marker} {task['text']}") + item.set_class(task["done"], "done") + save_tasks(self.tasks) + + def action_delete(self) -> None: + list_view = self.query_one("#tasks", ListView) + index = list_view.index + if index is None: + return + del self.tasks[index] + list_view.pop(index) + save_tasks(self.tasks) + + +def main(): + TodoApp().run() + + +if __name__ == "__main__": + main() diff --git a/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/app.tcss b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/app.tcss new file mode 100644 index 0000000000..3706ea2fd9 --- /dev/null +++ b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/app.tcss @@ -0,0 +1,40 @@ +/* todo_app/app.tcss */ + +Screen { + background: #1e1e2e; + color: #cdd6f4; +} + +#title { + width: 100%; + height: 1; + background: #45475a; + color: #f5e0dc; + text-style: bold; +} + +#new-task { + border: round #6c7086; + background: #181825; + color: #cdd6f4; +} + +#tasks { + height: 1fr; + border: round #6c7086; + background: #1e1e2e; +} + +#tasks > ListItem { + padding: 0 1; +} + +#tasks > ListItem.done { + color: #6c7086; + text-style: strike; +} + +Footer { + background: #45475a; + color: #cdd6f4; +} \ No newline at end of file diff --git a/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/storage.py b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/storage.py new file mode 100644 index 0000000000..b84936046e --- /dev/null +++ b/using-git-worktrees-to-run-parallel-ai-agents/todo-app/todo_app/storage.py @@ -0,0 +1,16 @@ +# todo_app/storage.py + +import json +from pathlib import Path + +DATA_FILE = Path("tasks.json") + + +def load_tasks(path=DATA_FILE): + if not path.exists(): + return [] + return json.loads(path.read_text(encoding="utf-8")) + + +def save_tasks(tasks, path=DATA_FILE): + path.write_text(json.dumps(tasks, indent=2), encoding="utf-8")