tasktree-manager 2.11.1__tar.gz → 2.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/CHANGELOG.md +6 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/PKG-INFO +1 -1
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/README.md +1 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/docs/configuration.md +2 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/docs/user-guide.md +1 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/pyproject.toml +1 -1
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/_version.py +2 -2
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/app.py +25 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/cli.py +1 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/commands.py +5 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/config.py +2 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/models.py +18 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/task_manager.py +16 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/create_modal.py +53 -1
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/task_list.py +5 -5
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_app.py +66 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_task_manager.py +42 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/SKILL.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/README.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/references/layouts.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/references/styling.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/references/widgets.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.github/templates/CHANGELOG.md.j2 +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.github/workflows/ci.yml +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.github/workflows/release.yml +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.gitignore +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/CONTRIBUTING.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/LICENSE +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/OPENSPEC.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/ROADMAP.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/docs/claude-code-integration-spec.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/docs/installation.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/docs/troubleshooting.md +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/mise.toml +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/scripts/bump_version.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/__init__.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/__init__.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/agent_sessions.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/claude_hooks.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/forge.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/git_ops.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/themes.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/__init__.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/app_header.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/messages_panel.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/setup_modal.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/status_panel.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/worktree_list.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/__init__.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/conftest.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_agent_sessions.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_claude_hooks.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_cli.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_commands.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_config.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_forge.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_git_ops.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_integration_git.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_memory.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_messages_panel.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_modals.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_setup_modal.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_status_panel.py +0 -0
- {tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tests/test_worktree_badges.py +0 -0
|
@@ -15,6 +15,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
15
15
|
- Git hooks support
|
|
16
16
|
- Custom task metadata
|
|
17
17
|
|
|
18
|
+
## [2.12.0] - 2026-07-31
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **tui**: Rename task display label with R
|
|
22
|
+
|
|
23
|
+
|
|
18
24
|
## [2.11.1] - 2026-07-31
|
|
19
25
|
|
|
20
26
|
### Fixed
|
|
@@ -113,6 +113,7 @@ a Claude Code statusline or shell prompt.
|
|
|
113
113
|
| `r` | Refresh | `s` | Sort tasks |
|
|
114
114
|
| `S` | Group worktrees | `m` | Messages |
|
|
115
115
|
| `t` | Cycle theme | `Tab` | Next panel |
|
|
116
|
+
| `R` | Rename task | | |
|
|
116
117
|
| `?` | Show help | `q` | Quit |
|
|
117
118
|
|
|
118
119
|
**Themes:** The default `tasktree` theme pairs a lazygit-classic ANSI palette with layered panels, numbered title bars, green focus borders, and a keycap footer. Five more design-system palettes ship tuned to the same shape — tokyo-night, catppuccin-mocha, gruvbox, dracula, nord — and every other Textual built-in theme works too (`Ctrl+P` to switch); the whole UI is styled with Textual design tokens, so any theme restyles it consistently.
|
|
@@ -223,6 +223,7 @@ help = "?" # Show help modal
|
|
|
223
223
|
new_task = "n" # Create new task
|
|
224
224
|
add_repo = "a" # Add repository to current task
|
|
225
225
|
delete_task = "d" # Delete/finish task
|
|
226
|
+
rename_task = "R" # Rename task display label (folder/branch unchanged, shift+r)
|
|
226
227
|
delete_worktree = "D" # Remove single worktree from task (shift+d)
|
|
227
228
|
|
|
228
229
|
# Git operations
|
|
@@ -583,6 +584,7 @@ All 22 customizable keybindings with descriptions:
|
|
|
583
584
|
| `clone_task` | `y` | Clone current task (same repos, new name) | `clone_task = "ctrl+y"` |
|
|
584
585
|
| `add_repo` | `a` | Add repository to current task | `add_repo = "ctrl+a"` |
|
|
585
586
|
| `delete_task` | `d` | Delete/finish current task | `delete_task = "delete"` |
|
|
587
|
+
| `rename_task` | `R` | Rename task display label (alias only) | `rename_task = "ctrl+r"` |
|
|
586
588
|
|
|
587
589
|
### Git Operations
|
|
588
590
|
|
|
@@ -300,6 +300,7 @@ what is displayed. Press `?` for the full reference at any time.
|
|
|
300
300
|
| `y` | Clone task | Create a new task pre-populated with the current task's repos |
|
|
301
301
|
| `a` | Add repository | Add additional repositories to the current task |
|
|
302
302
|
| `d` | Delete/finish | Delete current task and remove worktrees |
|
|
303
|
+
| `R` | Rename task | Set a display alias shown in the task list (folder/branch unchanged; empty input reverts) |
|
|
303
304
|
|
|
304
305
|
**Task creation workflow:**
|
|
305
306
|
1. Press `n`
|
|
@@ -70,7 +70,7 @@ version-file = "tasktree_manager/_version.py"
|
|
|
70
70
|
# Semantic Release Configuration
|
|
71
71
|
[tool.semantic_release]
|
|
72
72
|
version_toml = ["pyproject.toml:tool.semantic_release.version"]
|
|
73
|
-
version = "2.
|
|
73
|
+
version = "2.12.0"
|
|
74
74
|
tag_format = "v{version}"
|
|
75
75
|
branch = "main"
|
|
76
76
|
commit_message = "chore(release): {version} [skip ci]"
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.12.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 12, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -34,6 +34,7 @@ from .widgets.create_modal import (
|
|
|
34
34
|
DispatchAgentModal,
|
|
35
35
|
HelpModal,
|
|
36
36
|
PushResultModal,
|
|
37
|
+
RenameTaskModal,
|
|
37
38
|
SafeDeleteModal,
|
|
38
39
|
)
|
|
39
40
|
from .widgets.messages_panel import MessageLevel, MessagesPanel
|
|
@@ -274,6 +275,7 @@ class TaskTreeApp(App):
|
|
|
274
275
|
Binding(kb.get("clone_task", "y"), "clone_task", "Clone", show=False),
|
|
275
276
|
Binding(kb.get("add_repo", "a"), "add_repo", "Add Repo", show=False),
|
|
276
277
|
Binding(kb.get("delete_task", "d"), "delete_task", "Delete", show=False),
|
|
278
|
+
Binding(kb.get("rename_task", "R"), "rename_task", "Rename", show=False),
|
|
277
279
|
Binding(kb.get("open_lazygit", "g"), "open_lazygit", "Lazygit", show=False),
|
|
278
280
|
Binding(kb.get("show_diff", "h"), "show_diff", "Diff", show=False),
|
|
279
281
|
Binding(kb.get("open_editor", "e"), "open_editor", "Editor", show=False),
|
|
@@ -320,6 +322,7 @@ class TaskTreeApp(App):
|
|
|
320
322
|
(kb.get("clone_task", "y"), "clone_task", "Clone"),
|
|
321
323
|
(kb.get("add_repo", "a"), "add_repo", "Add Repo"),
|
|
322
324
|
(kb.get("delete_task", "d"), "delete_task", "Delete"),
|
|
325
|
+
(kb.get("rename_task", "R"), "rename_task", "Rename"),
|
|
323
326
|
(kb.get("show_diff", "h"), "show_diff", "Diff"),
|
|
324
327
|
(kb.get("open_claude_resume", "c"), "open_claude_resume", "Claude"),
|
|
325
328
|
(kb.get("cycle_sort", "s"), "cycle_sort", "Sort"),
|
|
@@ -1190,6 +1193,28 @@ class TaskTreeApp(App):
|
|
|
1190
1193
|
|
|
1191
1194
|
self.push_screen(ConfirmModal(title, message), handle_confirm)
|
|
1192
1195
|
|
|
1196
|
+
def action_rename_task(self) -> None:
|
|
1197
|
+
"""Set a display alias for the current task (folder/branch unchanged)."""
|
|
1198
|
+
if not self.current_task:
|
|
1199
|
+
self.notify("No task selected", severity="warning")
|
|
1200
|
+
return
|
|
1201
|
+
task = self.current_task
|
|
1202
|
+
|
|
1203
|
+
def handle_rename(new_name: str | None) -> None:
|
|
1204
|
+
if new_name is None:
|
|
1205
|
+
return
|
|
1206
|
+
try:
|
|
1207
|
+
self.task_manager.set_task_display_name(task, new_name)
|
|
1208
|
+
except OSError as e:
|
|
1209
|
+
self.notify(escape(f"Rename failed: {e}"), severity="error")
|
|
1210
|
+
return
|
|
1211
|
+
label = task.display_label
|
|
1212
|
+
self.notify(escape(f"Task '{task.name}' shown as '{label}'"))
|
|
1213
|
+
self._log_activity(f"Task '{task.name}' renamed to '{label}'", MessageLevel.INFO)
|
|
1214
|
+
self._run_periodic_refresh(force_ui=True)
|
|
1215
|
+
|
|
1216
|
+
self.push_screen(RenameTaskModal(task.name, task.display_name), handle_rename)
|
|
1217
|
+
|
|
1193
1218
|
def _finish_task(self, task: Task, force: bool = False) -> None:
|
|
1194
1219
|
"""Kick off task deletion in a background thread."""
|
|
1195
1220
|
if not self._begin_mutation("task-list"):
|
|
@@ -155,6 +155,7 @@ def cmd_list(manager: TaskManager, config: Config, args: argparse.Namespace) ->
|
|
|
155
155
|
payload = [
|
|
156
156
|
{
|
|
157
157
|
"name": task.name,
|
|
158
|
+
"display_name": task.display_name,
|
|
158
159
|
"path": str(task.path),
|
|
159
160
|
"repos": [wt.name for wt in task.worktrees],
|
|
160
161
|
"dirty": dirty[task.name],
|
|
@@ -12,6 +12,11 @@ APP_COMMANDS: list[tuple[str, str, str]] = [
|
|
|
12
12
|
("clone_task", "Clone Task", "Create a new task with the same repos as the current one"),
|
|
13
13
|
("add_repo", "Add Repo", "Add repositories to the current task"),
|
|
14
14
|
("delete_task", "Delete Task", "Delete the current task (with safety checks)"),
|
|
15
|
+
(
|
|
16
|
+
"rename_task",
|
|
17
|
+
"Rename Task",
|
|
18
|
+
"Set a display alias for the current task (folder/branch unchanged)",
|
|
19
|
+
),
|
|
15
20
|
("delete_worktree", "Delete Worktree", "Delete the selected worktree from the task"),
|
|
16
21
|
("open_lazygit", "Open Lazygit", "Open lazygit in the selected worktree"),
|
|
17
22
|
(
|
|
@@ -23,6 +23,7 @@ DEFAULT_KEYBINDINGS: dict[str, str] = {
|
|
|
23
23
|
"clone_task": "y",
|
|
24
24
|
"add_repo": "a",
|
|
25
25
|
"delete_task": "d",
|
|
26
|
+
"rename_task": "R",
|
|
26
27
|
"open_lazygit": "g",
|
|
27
28
|
"show_diff": "h",
|
|
28
29
|
"open_folder": "o",
|
|
@@ -424,6 +425,7 @@ new_task = "{self._toml_escape(self.keybindings.get("new_task", "n"))}"
|
|
|
424
425
|
clone_task = "{self._toml_escape(self.keybindings.get("clone_task", "y"))}"
|
|
425
426
|
add_repo = "{self._toml_escape(self.keybindings.get("add_repo", "a"))}"
|
|
426
427
|
delete_task = "{self._toml_escape(self.keybindings.get("delete_task", "d"))}"
|
|
428
|
+
rename_task = "{self._toml_escape(self.keybindings.get("rename_task", "R"))}"
|
|
427
429
|
open_lazygit = "{self._toml_escape(self.keybindings.get("open_lazygit", "g"))}"
|
|
428
430
|
show_diff = "{self._toml_escape(self.keybindings.get("show_diff", "h"))}"
|
|
429
431
|
open_folder = "{self._toml_escape(self.keybindings.get("open_folder", "o"))}"
|
|
@@ -48,6 +48,24 @@ class Task:
|
|
|
48
48
|
"""Check if the task has a CLAUDE.md file."""
|
|
49
49
|
return (self.path / "CLAUDE.md").exists()
|
|
50
50
|
|
|
51
|
+
@property
|
|
52
|
+
def display_name(self) -> str | None:
|
|
53
|
+
"""Display alias from <task>/.tasktree_name, or None when unset.
|
|
54
|
+
|
|
55
|
+
Read lazily from disk so every Task construction site picks it up
|
|
56
|
+
and a rename is visible on the next render without reloading.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
value = (self.path / ".tasktree_name").read_text(encoding="utf-8").strip()
|
|
60
|
+
except OSError:
|
|
61
|
+
return None
|
|
62
|
+
return value or None
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def display_label(self) -> str:
|
|
66
|
+
"""What the task list shows: the alias when set, else the real name."""
|
|
67
|
+
return self.display_name or self.name
|
|
68
|
+
|
|
51
69
|
|
|
52
70
|
@dataclass
|
|
53
71
|
class GitStatus:
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/task_manager.py
RENAMED
|
@@ -337,6 +337,22 @@ class TaskManager:
|
|
|
337
337
|
if task.path.exists():
|
|
338
338
|
shutil.rmtree(task.path)
|
|
339
339
|
|
|
340
|
+
def set_task_display_name(self, task: Task, display_name: str | None) -> None:
|
|
341
|
+
"""Set or clear the task's display alias (TUI label only).
|
|
342
|
+
|
|
343
|
+
The folder, branches and worktrees keep the real task name; the alias
|
|
344
|
+
lives in <task>/.tasktree_name. Empty input — or an alias equal to the
|
|
345
|
+
real name — clears the file.
|
|
346
|
+
"""
|
|
347
|
+
marker = task.path / ".tasktree_name"
|
|
348
|
+
name = (display_name or "").strip()
|
|
349
|
+
if name == task.name:
|
|
350
|
+
name = ""
|
|
351
|
+
if name:
|
|
352
|
+
marker.write_text(name + "\n", encoding="utf-8")
|
|
353
|
+
elif marker.exists():
|
|
354
|
+
marker.unlink()
|
|
355
|
+
|
|
340
356
|
def archive_task(self, task: Task, notes: list[str] | None = None) -> Path | None:
|
|
341
357
|
"""Write the task's combined diff to the archive directory.
|
|
342
358
|
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/create_modal.py
RENAMED
|
@@ -462,6 +462,53 @@ class SafeDeleteModal(ThemedModalScreen[str | None]):
|
|
|
462
462
|
self.dismiss("force")
|
|
463
463
|
|
|
464
464
|
|
|
465
|
+
class RenameTaskModal(ThemedModalScreen[str | None]):
|
|
466
|
+
"""Prompt for a task's display alias (TUI label only).
|
|
467
|
+
|
|
468
|
+
Dismisses with the new alias, "" to revert to the real name, or None
|
|
469
|
+
when cancelled.
|
|
470
|
+
"""
|
|
471
|
+
|
|
472
|
+
CANCEL_RESULT: ClassVar[None] = None
|
|
473
|
+
|
|
474
|
+
def __init__(self, task_name: str, current_alias: str | None, *args, **kwargs):
|
|
475
|
+
super().__init__(*args, **kwargs)
|
|
476
|
+
self.task_name = task_name
|
|
477
|
+
self.current_alias = current_alias
|
|
478
|
+
|
|
479
|
+
def compose(self) -> ComposeResult:
|
|
480
|
+
with Container():
|
|
481
|
+
yield Label("Rename Task (display only)", classes="modal-title")
|
|
482
|
+
yield Static(
|
|
483
|
+
escape(f"Folder/branch stay '{self.task_name}'; empty input reverts."),
|
|
484
|
+
classes="modal-message",
|
|
485
|
+
)
|
|
486
|
+
yield Input(
|
|
487
|
+
value=self.current_alias or "",
|
|
488
|
+
placeholder=self.task_name,
|
|
489
|
+
id="rename-input",
|
|
490
|
+
)
|
|
491
|
+
with Horizontal(classes="button-row"):
|
|
492
|
+
yield Button("Rename", variant="primary", id="rename-btn")
|
|
493
|
+
yield Button("Cancel", variant="default", id="cancel-btn")
|
|
494
|
+
|
|
495
|
+
def on_mount(self) -> None:
|
|
496
|
+
super().on_mount()
|
|
497
|
+
self.query_one("#rename-input", Input).focus()
|
|
498
|
+
|
|
499
|
+
def _submit(self) -> None:
|
|
500
|
+
self.dismiss(self.query_one("#rename-input", Input).value.strip())
|
|
501
|
+
|
|
502
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
503
|
+
self._submit()
|
|
504
|
+
|
|
505
|
+
def on_button_pressed(self, event: Button.Pressed) -> None:
|
|
506
|
+
if event.button.id == "rename-btn":
|
|
507
|
+
self._submit()
|
|
508
|
+
elif event.button.id == "cancel-btn":
|
|
509
|
+
self.dismiss(None)
|
|
510
|
+
|
|
511
|
+
|
|
465
512
|
class DispatchAgentModal(ThemedModalScreen[str | None]):
|
|
466
513
|
"""Prompt for dispatching a background Claude agent into a worktree.
|
|
467
514
|
|
|
@@ -669,7 +716,12 @@ class HelpModal(ThemedModalScreen[None]):
|
|
|
669
716
|
task_section += (
|
|
670
717
|
self._format_binding("add_repo", "a", "Add repository to current task") + "\n"
|
|
671
718
|
)
|
|
672
|
-
task_section +=
|
|
719
|
+
task_section += (
|
|
720
|
+
self._format_binding("delete_task", "d", "Delete/finish current task") + "\n"
|
|
721
|
+
)
|
|
722
|
+
task_section += self._format_binding(
|
|
723
|
+
"rename_task", "R", "Rename task display label (folder/branch unchanged)"
|
|
724
|
+
)
|
|
673
725
|
sections.append(task_section)
|
|
674
726
|
|
|
675
727
|
# Tools section
|
|
@@ -88,10 +88,10 @@ class TaskList(OptionList):
|
|
|
88
88
|
hook_indicator = " [green]✓[/]"
|
|
89
89
|
else:
|
|
90
90
|
hook_indicator = ""
|
|
91
|
-
# Escape the
|
|
92
|
-
#
|
|
91
|
+
# Escape the label: task directories (and aliases) can hold
|
|
92
|
+
# markup-significant brackets which must not style (or crash)
|
|
93
93
|
# the list rendering
|
|
94
|
-
name = escape(task.
|
|
94
|
+
name = escape(task.display_label)
|
|
95
95
|
if task.is_dirty:
|
|
96
96
|
prompt = f"[red]●[/]{claude_md}{name} [red]({task.dirty_count})[/]{hook_indicator}"
|
|
97
97
|
else:
|
|
@@ -220,9 +220,9 @@ class TaskList(OptionList):
|
|
|
220
220
|
"""Sort tasks according to current sort mode."""
|
|
221
221
|
match self._sort_mode:
|
|
222
222
|
case SortMode.NAME_ASC:
|
|
223
|
-
return sorted(tasks, key=lambda t: t.
|
|
223
|
+
return sorted(tasks, key=lambda t: t.display_label.lower())
|
|
224
224
|
case SortMode.NAME_DESC:
|
|
225
|
-
return sorted(tasks, key=lambda t: t.
|
|
225
|
+
return sorted(tasks, key=lambda t: t.display_label.lower(), reverse=True)
|
|
226
226
|
case SortMode.DATE_NEWEST:
|
|
227
227
|
return sorted(
|
|
228
228
|
tasks,
|
|
@@ -392,6 +392,72 @@ class TestOpenExternalCommands:
|
|
|
392
392
|
assert app.is_running
|
|
393
393
|
|
|
394
394
|
|
|
395
|
+
class TestRenameTaskAction:
|
|
396
|
+
"""Tests for the rename (display alias) action."""
|
|
397
|
+
|
|
398
|
+
async def test_rename_via_keybinding(self, app, sample_repo, task_manager):
|
|
399
|
+
"""R -> modal -> alias shows in the task list; folder unchanged."""
|
|
400
|
+
from tasktree_manager.widgets.create_modal import RenameTaskModal
|
|
401
|
+
|
|
402
|
+
repo_path, branch = sample_repo
|
|
403
|
+
task = task_manager.create_task("RENAME-TASK", ["sample-repo"], branch)
|
|
404
|
+
|
|
405
|
+
async with app.run_test() as pilot:
|
|
406
|
+
await pilot.pause()
|
|
407
|
+
await pilot.press("R")
|
|
408
|
+
await pilot.pause()
|
|
409
|
+
assert isinstance(app.screen_stack[-1], RenameTaskModal)
|
|
410
|
+
|
|
411
|
+
await pilot.press(*"nice label")
|
|
412
|
+
await pilot.press("enter")
|
|
413
|
+
await app.workers.wait_for_complete()
|
|
414
|
+
await pilot.pause()
|
|
415
|
+
|
|
416
|
+
assert (task.path / ".tasktree_name").read_text().strip() == "nice label"
|
|
417
|
+
assert task.path.exists() # folder untouched
|
|
418
|
+
task_list = app.query_one("#task-list", TaskList)
|
|
419
|
+
prompt = str(task_list.get_option("RENAME-TASK").prompt)
|
|
420
|
+
assert "nice label" in prompt
|
|
421
|
+
assert "RENAME-TASK" not in prompt
|
|
422
|
+
|
|
423
|
+
async def test_rename_cancel_changes_nothing(self, app, sample_repo, task_manager):
|
|
424
|
+
repo_path, branch = sample_repo
|
|
425
|
+
task = task_manager.create_task("RENAME-CANCEL", ["sample-repo"], branch)
|
|
426
|
+
|
|
427
|
+
async with app.run_test() as pilot:
|
|
428
|
+
await pilot.pause()
|
|
429
|
+
await pilot.press("R")
|
|
430
|
+
await pilot.pause()
|
|
431
|
+
await pilot.press("escape")
|
|
432
|
+
await pilot.pause()
|
|
433
|
+
assert not (task.path / ".tasktree_name").exists()
|
|
434
|
+
|
|
435
|
+
async def test_rename_empty_reverts(self, app, sample_repo, task_manager):
|
|
436
|
+
repo_path, branch = sample_repo
|
|
437
|
+
task = task_manager.create_task("RENAME-REVERT", ["sample-repo"], branch)
|
|
438
|
+
task_manager.set_task_display_name(task, "old label")
|
|
439
|
+
|
|
440
|
+
async with app.run_test() as pilot:
|
|
441
|
+
await pilot.pause()
|
|
442
|
+
await pilot.press("R")
|
|
443
|
+
await pilot.pause()
|
|
444
|
+
# Prefilled with the current alias - clear it
|
|
445
|
+
from textual.widgets import Input
|
|
446
|
+
|
|
447
|
+
from tasktree_manager.widgets.create_modal import RenameTaskModal
|
|
448
|
+
|
|
449
|
+
modal = app.screen_stack[-1]
|
|
450
|
+
assert isinstance(modal, RenameTaskModal)
|
|
451
|
+
modal.query_one("#rename-input", Input).value = ""
|
|
452
|
+
await pilot.press("enter")
|
|
453
|
+
await app.workers.wait_for_complete()
|
|
454
|
+
await pilot.pause()
|
|
455
|
+
|
|
456
|
+
assert not (task.path / ".tasktree_name").exists()
|
|
457
|
+
task_list = app.query_one("#task-list", TaskList)
|
|
458
|
+
assert "RENAME-REVERT" in str(task_list.get_option("RENAME-REVERT").prompt)
|
|
459
|
+
|
|
460
|
+
|
|
395
461
|
class TestShowDiffAction:
|
|
396
462
|
"""Tests for the hunk diff action (h)."""
|
|
397
463
|
|
|
@@ -433,6 +433,48 @@ class TestTaskBaseRecording:
|
|
|
433
433
|
assert GitOps.get_task_base(worktree_from_repo, "no-such-branch") is None
|
|
434
434
|
|
|
435
435
|
|
|
436
|
+
class TestDisplayName:
|
|
437
|
+
"""Display alias: TUI label only, folder/branch untouched."""
|
|
438
|
+
|
|
439
|
+
def test_set_and_read_alias(self, task_manager, sample_repo):
|
|
440
|
+
repo_path, branch = sample_repo
|
|
441
|
+
task = task_manager.create_task("DIC-1901-argocd-tls", ["sample-repo"], branch)
|
|
442
|
+
|
|
443
|
+
task_manager.set_task_display_name(task, "ArgoCD TLS rollout")
|
|
444
|
+
assert task.display_name == "ArgoCD TLS rollout"
|
|
445
|
+
assert task.display_label == "ArgoCD TLS rollout"
|
|
446
|
+
# Real identity untouched
|
|
447
|
+
assert task.name == "DIC-1901-argocd-tls"
|
|
448
|
+
assert task.path.name == "DIC-1901-argocd-tls"
|
|
449
|
+
assert (task.path / ".tasktree_name").exists()
|
|
450
|
+
# A fresh load sees the alias too (read lazily from disk)
|
|
451
|
+
reloaded = task_manager.get_task("DIC-1901-argocd-tls")
|
|
452
|
+
assert reloaded is not None
|
|
453
|
+
assert reloaded.display_label == "ArgoCD TLS rollout"
|
|
454
|
+
|
|
455
|
+
def test_clear_alias_with_empty(self, task_manager, sample_repo):
|
|
456
|
+
repo_path, branch = sample_repo
|
|
457
|
+
task = task_manager.create_task("ALIAS-CLEAR", ["sample-repo"], branch)
|
|
458
|
+
task_manager.set_task_display_name(task, "temp label")
|
|
459
|
+
task_manager.set_task_display_name(task, "")
|
|
460
|
+
assert task.display_name is None
|
|
461
|
+
assert task.display_label == "ALIAS-CLEAR"
|
|
462
|
+
assert not (task.path / ".tasktree_name").exists()
|
|
463
|
+
|
|
464
|
+
def test_alias_equal_to_real_name_clears(self, task_manager, sample_repo):
|
|
465
|
+
repo_path, branch = sample_repo
|
|
466
|
+
task = task_manager.create_task("ALIAS-SAME", ["sample-repo"], branch)
|
|
467
|
+
task_manager.set_task_display_name(task, "ALIAS-SAME")
|
|
468
|
+
assert not (task.path / ".tasktree_name").exists()
|
|
469
|
+
assert task.display_label == "ALIAS-SAME"
|
|
470
|
+
|
|
471
|
+
def test_no_alias_by_default(self, task_manager, sample_repo):
|
|
472
|
+
repo_path, branch = sample_repo
|
|
473
|
+
task = task_manager.create_task("ALIAS-NONE", ["sample-repo"], branch)
|
|
474
|
+
assert task.display_name is None
|
|
475
|
+
assert task.display_label == "ALIAS-NONE"
|
|
476
|
+
|
|
477
|
+
|
|
436
478
|
class TestForgeAwareSafety:
|
|
437
479
|
"""Squash/rebase merges detected via the forge (glab/gh) MR state."""
|
|
438
480
|
|
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/data_viewer.py
RENAMED
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/todo_app.py
RENAMED
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/assets/worker_demo.py
RENAMED
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/references/layouts.md
RENAMED
|
File without changes
|
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/references/styling.md
RENAMED
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/.claude/skills/textual-tui/references/widgets.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/agent_sessions.py
RENAMED
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/services/claude_hooks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/messages_panel.py
RENAMED
|
File without changes
|
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/status_panel.py
RENAMED
|
File without changes
|
{tasktree_manager-2.11.1 → tasktree_manager-2.12.0}/tasktree_manager/widgets/worktree_list.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|