tasktree-manager 2.3.0__tar.gz → 2.4.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.3.0 → tasktree_manager-2.4.0}/CHANGELOG.md +6 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/PKG-INFO +1 -1
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/docs/user-guide.md +6 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/pyproject.toml +1 -1
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/_version.py +2 -2
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/app.py +55 -30
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/widgets/task_list.py +15 -1
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/widgets/worktree_list.py +14 -1
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_app.py +93 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/SKILL.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/README.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/dashboard_app.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/data_viewer.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/todo_app.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/worker_demo.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/references/layouts.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/references/official-guides-index.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/references/styling.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/references/widgets.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.github/templates/CHANGELOG.md.j2 +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.github/workflows/ci.yml +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.github/workflows/release.yml +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.gitignore +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/CONTRIBUTING.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/LICENSE +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/OPENSPEC.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/README.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/ROADMAP.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/docs/claude-code-integration-spec.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/docs/configuration.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/docs/installation.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/docs/troubleshooting.md +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/mise.toml +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/scripts/bump_version.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/__init__.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/services/__init__.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/services/claude_hooks.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/services/config.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/services/git_ops.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/services/models.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/services/task_manager.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/widgets/__init__.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/widgets/create_modal.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/widgets/messages_panel.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/widgets/setup_modal.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tasktree_manager/widgets/status_panel.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/__init__.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/conftest.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_config.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_git_ops.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_integration_git.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_memory.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_messages_panel.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_modals.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_setup_modal.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_status_panel.py +0 -0
- {tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/tests/test_task_manager.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.4.0] - 2026-06-10
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Context-sensitive footer showing the focused panel's keys (#6)
|
|
22
|
+
|
|
23
|
+
|
|
18
24
|
## [2.3.0] - 2026-06-10
|
|
19
25
|
|
|
20
26
|
### Added
|
|
@@ -246,6 +246,12 @@ Shows detailed git status for the selected worktree:
|
|
|
246
246
|
|
|
247
247
|
All keybindings can be customized in [`config.toml`](configuration.md).
|
|
248
248
|
|
|
249
|
+
The footer is context-sensitive, lazygit-style: it shows the actions for the
|
|
250
|
+
focused panel (task actions in the Task List, worktree/git actions in the
|
|
251
|
+
Worktree List) plus the always-available `r` Refresh, `?` Help, and `q` Quit.
|
|
252
|
+
Every key works regardless of which panel has focus — the footer only changes
|
|
253
|
+
what is displayed. Press `?` for the full reference at any time.
|
|
254
|
+
|
|
249
255
|
### Navigation
|
|
250
256
|
|
|
251
257
|
| Key | Action | Description |
|
|
@@ -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.4.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.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 4, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -210,28 +210,30 @@ class TaskTreeApp(App):
|
|
|
210
210
|
}
|
|
211
211
|
"""
|
|
212
212
|
|
|
213
|
-
# Default bindings - will be overridden in __init__ with config values
|
|
214
|
-
#
|
|
215
|
-
#
|
|
213
|
+
# Default bindings - will be overridden in __init__ with config values.
|
|
214
|
+
# The footer is context-sensitive: app-level bindings keep every key
|
|
215
|
+
# working globally but stay hidden, except the always-visible globals
|
|
216
|
+
# (refresh/help/quit). The focused panel contributes its own visible
|
|
217
|
+
# bindings (see compose), lazygit-style.
|
|
216
218
|
BINDINGS = [
|
|
217
|
-
Binding("n", "new_task", "New
|
|
218
|
-
Binding("y", "clone_task", "Clone
|
|
219
|
-
Binding("a", "add_repo", "Add Repo"),
|
|
220
|
-
Binding("d", "delete_task", "Delete
|
|
221
|
-
Binding("g", "open_lazygit", "Lazygit"),
|
|
222
|
-
Binding("e", "open_editor", "Editor"),
|
|
223
|
-
Binding("o", "open_folder", "Open"),
|
|
224
|
-
Binding("c", "open_claude_resume", "Claude"),
|
|
225
|
-
Binding("p", "push_all", "Push
|
|
219
|
+
Binding("n", "new_task", "New", show=False),
|
|
220
|
+
Binding("y", "clone_task", "Clone", show=False),
|
|
221
|
+
Binding("a", "add_repo", "Add Repo", show=False),
|
|
222
|
+
Binding("d", "delete_task", "Delete", show=False),
|
|
223
|
+
Binding("g", "open_lazygit", "Lazygit", show=False),
|
|
224
|
+
Binding("e", "open_editor", "Editor", show=False),
|
|
225
|
+
Binding("o", "open_folder", "Open", show=False),
|
|
226
|
+
Binding("c", "open_claude_resume", "Claude", show=False),
|
|
227
|
+
Binding("p", "push_all", "Push", show=False),
|
|
226
228
|
Binding("r", "refresh", "Refresh"),
|
|
227
|
-
Binding("m", "toggle_messages", "Messages"),
|
|
229
|
+
Binding("m", "toggle_messages", "Messages", show=False),
|
|
228
230
|
Binding("q", "quit", "Quit"),
|
|
229
231
|
Binding("?", "help", "Help"),
|
|
230
232
|
Binding("enter", "open_shell", "Shell", show=False),
|
|
231
233
|
Binding("C", "open_claude_gui_code", "Claude GUI", show=False),
|
|
232
234
|
Binding("P", "pull_all", "Pull All", show=False),
|
|
233
235
|
Binding("S", "toggle_grouping", "Group", show=False),
|
|
234
|
-
Binding("D", "delete_worktree", "Del
|
|
236
|
+
Binding("D", "delete_worktree", "Del WT", show=False),
|
|
235
237
|
Binding("tab", "focus_next", "Next Panel", show=False),
|
|
236
238
|
Binding("shift+tab", "focus_previous", "Prev Panel", show=False),
|
|
237
239
|
Binding("j", "cursor_down", "Down", show=False),
|
|
@@ -264,26 +266,28 @@ class TaskTreeApp(App):
|
|
|
264
266
|
self._last_tasks_fingerprint: tuple | None = None
|
|
265
267
|
|
|
266
268
|
def _build_bindings_from_config(self) -> list[Binding]:
|
|
267
|
-
"""Build keybindings
|
|
269
|
+
"""Build app-level keybindings from config.
|
|
270
|
+
|
|
271
|
+
Every key works globally regardless of focus, but only the global
|
|
272
|
+
actions (refresh/help/quit) are shown in the footer; the focused
|
|
273
|
+
panel contributes its own visible bindings (see compose).
|
|
268
274
|
|
|
269
275
|
Returns:
|
|
270
276
|
List of Binding objects with keys from config
|
|
271
277
|
"""
|
|
272
278
|
kb = self.config.keybindings
|
|
273
|
-
# Footer order: n, a, d, g, o, c, p, r, m, q, ?
|
|
274
|
-
# Uppercase shown only when shift is pressed: C, P, S
|
|
275
279
|
return [
|
|
276
|
-
Binding(kb.get("new_task", "n"), "new_task", "New
|
|
277
|
-
Binding(kb.get("clone_task", "y"), "clone_task", "Clone
|
|
278
|
-
Binding(kb.get("add_repo", "a"), "add_repo", "Add Repo"),
|
|
279
|
-
Binding(kb.get("delete_task", "d"), "delete_task", "Delete
|
|
280
|
-
Binding(kb.get("open_lazygit", "g"), "open_lazygit", "Lazygit"),
|
|
281
|
-
Binding(kb.get("open_editor", "e"), "open_editor", "Editor"),
|
|
282
|
-
Binding(kb.get("open_folder", "o"), "open_folder", "Open"),
|
|
283
|
-
Binding(kb.get("open_claude_resume", "c"), "open_claude_resume", "Claude"),
|
|
284
|
-
Binding(kb.get("push_all", "p"), "push_all", "Push
|
|
280
|
+
Binding(kb.get("new_task", "n"), "new_task", "New", show=False),
|
|
281
|
+
Binding(kb.get("clone_task", "y"), "clone_task", "Clone", show=False),
|
|
282
|
+
Binding(kb.get("add_repo", "a"), "add_repo", "Add Repo", show=False),
|
|
283
|
+
Binding(kb.get("delete_task", "d"), "delete_task", "Delete", show=False),
|
|
284
|
+
Binding(kb.get("open_lazygit", "g"), "open_lazygit", "Lazygit", show=False),
|
|
285
|
+
Binding(kb.get("open_editor", "e"), "open_editor", "Editor", show=False),
|
|
286
|
+
Binding(kb.get("open_folder", "o"), "open_folder", "Open", show=False),
|
|
287
|
+
Binding(kb.get("open_claude_resume", "c"), "open_claude_resume", "Claude", show=False),
|
|
288
|
+
Binding(kb.get("push_all", "p"), "push_all", "Push", show=False),
|
|
285
289
|
Binding(kb.get("refresh", "r"), "refresh", "Refresh"),
|
|
286
|
-
Binding(kb.get("toggle_messages", "m"), "toggle_messages", "Messages"),
|
|
290
|
+
Binding(kb.get("toggle_messages", "m"), "toggle_messages", "Messages", show=False),
|
|
287
291
|
Binding(kb.get("quit", "q"), "quit", "Quit"),
|
|
288
292
|
Binding(kb.get("help", "?"), "help", "Help"),
|
|
289
293
|
Binding(kb.get("open_shell", "enter"), "open_shell", "Shell", show=False),
|
|
@@ -295,7 +299,7 @@ class TaskTreeApp(App):
|
|
|
295
299
|
),
|
|
296
300
|
Binding(kb.get("pull_all", "P"), "pull_all", "Pull All", show=False),
|
|
297
301
|
Binding(kb.get("toggle_grouping", "S"), "toggle_grouping", "Group", show=False),
|
|
298
|
-
Binding(kb.get("delete_worktree", "D"), "delete_worktree", "Del
|
|
302
|
+
Binding(kb.get("delete_worktree", "D"), "delete_worktree", "Del WT", show=False),
|
|
299
303
|
Binding(kb.get("focus_next", "tab"), "focus_next", "Next Panel", show=False),
|
|
300
304
|
Binding(
|
|
301
305
|
kb.get("focus_previous", "shift+tab"), "focus_previous", "Prev Panel", show=False
|
|
@@ -312,16 +316,37 @@ class TaskTreeApp(App):
|
|
|
312
316
|
|
|
313
317
|
def compose(self) -> ComposeResult:
|
|
314
318
|
"""Compose the app layout."""
|
|
319
|
+
kb = self.config.keybindings
|
|
320
|
+
# Footer bindings shown only while the respective panel has focus.
|
|
321
|
+
# The keys themselves also exist app-level (hidden) so they work
|
|
322
|
+
# regardless of focus; these entries only add footer visibility.
|
|
323
|
+
task_panel_bindings = [
|
|
324
|
+
(kb.get("new_task", "n"), "new_task", "New"),
|
|
325
|
+
(kb.get("clone_task", "y"), "clone_task", "Clone"),
|
|
326
|
+
(kb.get("add_repo", "a"), "add_repo", "Add Repo"),
|
|
327
|
+
(kb.get("delete_task", "d"), "delete_task", "Delete"),
|
|
328
|
+
(kb.get("open_claude_resume", "c"), "open_claude_resume", "Claude"),
|
|
329
|
+
(kb.get("cycle_sort", "s"), "cycle_sort", "Sort"),
|
|
330
|
+
]
|
|
331
|
+
worktree_panel_bindings = [
|
|
332
|
+
(kb.get("open_lazygit", "g"), "open_lazygit", "Lazygit"),
|
|
333
|
+
(kb.get("open_editor", "e"), "open_editor", "Editor"),
|
|
334
|
+
(kb.get("open_folder", "o"), "open_folder", "Open"),
|
|
335
|
+
(kb.get("open_shell", "enter"), "open_shell", "Shell"),
|
|
336
|
+
(kb.get("push_all", "p"), "push_all", "Push"),
|
|
337
|
+
(kb.get("delete_worktree", "D"), "delete_worktree", "Del WT"),
|
|
338
|
+
]
|
|
339
|
+
|
|
315
340
|
yield Header()
|
|
316
341
|
with Horizontal(id="main-container"):
|
|
317
342
|
# Left column: Tasks (top) and Worktrees (bottom)
|
|
318
343
|
with Vertical(id="left-column"):
|
|
319
344
|
with Vertical(id="task-panel"):
|
|
320
345
|
yield Static("Tasks", classes="panel-title", id="task-panel-title")
|
|
321
|
-
yield TaskList(id="task-list")
|
|
346
|
+
yield TaskList(id="task-list", context_bindings=task_panel_bindings)
|
|
322
347
|
with Vertical(id="worktree-panel"):
|
|
323
348
|
yield Static("Worktrees", classes="panel-title", id="worktree-panel-title")
|
|
324
|
-
yield WorktreeList(id="worktree-list")
|
|
349
|
+
yield WorktreeList(id="worktree-list", context_bindings=worktree_panel_bindings)
|
|
325
350
|
# Right column: Info/Status panel
|
|
326
351
|
with Vertical(id="right-column"):
|
|
327
352
|
with Vertical(id="status-panel"):
|
|
@@ -45,12 +45,26 @@ class TaskList(OptionList):
|
|
|
45
45
|
self.label = label
|
|
46
46
|
super().__init__()
|
|
47
47
|
|
|
48
|
-
def __init__(
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*args,
|
|
51
|
+
context_bindings: list[tuple[str, str, str]] | None = None,
|
|
52
|
+
**kwargs,
|
|
53
|
+
):
|
|
49
54
|
super().__init__(*args, **kwargs)
|
|
50
55
|
self.tasks: list[Task] = []
|
|
51
56
|
self._sort_mode: SortMode = SortMode.NAME_ASC
|
|
52
57
|
# Last known Claude session statuses, kept so indicators survive reloads
|
|
53
58
|
self._claude_statuses: dict[str, str] = {}
|
|
59
|
+
# Footer-visible (key, app action, description) bindings shown while
|
|
60
|
+
# this panel has focus; the keys also exist app-level (hidden) so
|
|
61
|
+
# they keep working regardless of focus
|
|
62
|
+
for key, action, description in context_bindings or []:
|
|
63
|
+
# Drop any inherited binding on the same key - dispatch and the
|
|
64
|
+
# footer both use the first binding found, so an OptionList
|
|
65
|
+
# default (e.g. "enter") would otherwise shadow the app action
|
|
66
|
+
self._bindings.key_to_bindings.pop(key, None)
|
|
67
|
+
self._bindings.bind(key, f"app.{action}", description, show=True)
|
|
54
68
|
|
|
55
69
|
@property
|
|
56
70
|
def index(self) -> int | None:
|
|
@@ -31,12 +31,25 @@ class WorktreeList(OptionList):
|
|
|
31
31
|
self.enabled = enabled
|
|
32
32
|
super().__init__()
|
|
33
33
|
|
|
34
|
-
def __init__(
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*args,
|
|
37
|
+
context_bindings: list[tuple[str, str, str]] | None = None,
|
|
38
|
+
**kwargs,
|
|
39
|
+
):
|
|
35
40
|
super().__init__(*args, **kwargs)
|
|
36
41
|
self.worktrees: list[Worktree] = []
|
|
37
42
|
self._grouping_enabled: bool = False
|
|
38
43
|
# Maps option index to worktree index (for handling headers)
|
|
39
44
|
self._option_to_worktree: dict[int, int] = {}
|
|
45
|
+
# Footer-visible (key, app action, description) bindings shown while
|
|
46
|
+
# this panel has focus; the keys also exist app-level (hidden) so
|
|
47
|
+
# they keep working regardless of focus. Replacing "enter" here also
|
|
48
|
+
# fixes shell opening: OptionList's own select binding used to
|
|
49
|
+
# swallow the key before the app's open_shell binding could see it.
|
|
50
|
+
for key, action, description in context_bindings or []:
|
|
51
|
+
self._bindings.key_to_bindings.pop(key, None)
|
|
52
|
+
self._bindings.bind(key, f"app.{action}", description, show=True)
|
|
40
53
|
|
|
41
54
|
@property
|
|
42
55
|
def index(self) -> int | None:
|
|
@@ -570,3 +570,96 @@ class TestAppConfiguration:
|
|
|
570
570
|
from tasktree_manager.services.git_ops import GitOps
|
|
571
571
|
|
|
572
572
|
assert GitOps.network_timeout == app.config.git_timeout
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
class TestContextSensitiveFooter:
|
|
576
|
+
"""Tests for the context-sensitive footer bindings."""
|
|
577
|
+
|
|
578
|
+
@staticmethod
|
|
579
|
+
def _shown_actions(app) -> set[str]:
|
|
580
|
+
"""Actions currently visible in the footer."""
|
|
581
|
+
return {
|
|
582
|
+
active.binding.action
|
|
583
|
+
for active in app.screen.active_bindings.values()
|
|
584
|
+
if active.binding.show
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async def test_task_panel_footer_shows_task_actions(self, app, sample_repo, task_manager):
|
|
588
|
+
"""Task list focused: task actions plus globals are visible."""
|
|
589
|
+
repo_path, branch = sample_repo
|
|
590
|
+
task_manager.create_task("FOOTER-TASKS", ["sample-repo"], branch)
|
|
591
|
+
|
|
592
|
+
async with app.run_test() as pilot:
|
|
593
|
+
await pilot.pause()
|
|
594
|
+
|
|
595
|
+
assert app.focused.id == "task-list"
|
|
596
|
+
shown = self._shown_actions(app)
|
|
597
|
+
assert "app.new_task" in shown
|
|
598
|
+
assert "app.clone_task" in shown
|
|
599
|
+
assert "app.delete_task" in shown
|
|
600
|
+
# Worktree-context actions are hidden in this context
|
|
601
|
+
assert "app.open_lazygit" not in shown
|
|
602
|
+
assert "app.open_shell" not in shown
|
|
603
|
+
# Globals always visible
|
|
604
|
+
assert "refresh" in shown
|
|
605
|
+
assert "help" in shown
|
|
606
|
+
assert "quit" in shown
|
|
607
|
+
|
|
608
|
+
async def test_worktree_panel_footer_shows_worktree_actions(
|
|
609
|
+
self, app, sample_repo, task_manager
|
|
610
|
+
):
|
|
611
|
+
"""Worktree list focused: worktree actions plus globals are visible."""
|
|
612
|
+
repo_path, branch = sample_repo
|
|
613
|
+
task_manager.create_task("FOOTER-WT", ["sample-repo"], branch)
|
|
614
|
+
|
|
615
|
+
async with app.run_test() as pilot:
|
|
616
|
+
await pilot.pause()
|
|
617
|
+
await pilot.press("tab")
|
|
618
|
+
await pilot.pause()
|
|
619
|
+
|
|
620
|
+
assert app.focused.id == "worktree-list"
|
|
621
|
+
shown = self._shown_actions(app)
|
|
622
|
+
assert "app.open_lazygit" in shown
|
|
623
|
+
assert "app.open_shell" in shown
|
|
624
|
+
assert "app.push_all" in shown
|
|
625
|
+
# Task-context actions are hidden in this context
|
|
626
|
+
assert "app.new_task" not in shown
|
|
627
|
+
# Globals always visible
|
|
628
|
+
assert "refresh" in shown
|
|
629
|
+
assert "quit" in shown
|
|
630
|
+
|
|
631
|
+
async def test_keys_work_outside_their_panel(self, app, sample_repos, task_manager):
|
|
632
|
+
"""A task-context key still works while the worktree list is focused."""
|
|
633
|
+
_, branch = sample_repos
|
|
634
|
+
task_manager.create_task("FOOTER-KEYS", ["repo-alpha"], branch)
|
|
635
|
+
|
|
636
|
+
async with app.run_test() as pilot:
|
|
637
|
+
await pilot.pause()
|
|
638
|
+
await pilot.press("tab") # focus worktree list
|
|
639
|
+
await pilot.pause()
|
|
640
|
+
|
|
641
|
+
# 'n' is a task-panel binding but must still open the modal
|
|
642
|
+
await pilot.press("n")
|
|
643
|
+
await pilot.pause()
|
|
644
|
+
|
|
645
|
+
create_modals = [s for s in app.screen_stack if isinstance(s, CreateTaskModal)]
|
|
646
|
+
assert len(create_modals) == 1
|
|
647
|
+
|
|
648
|
+
async def test_enter_opens_shell_from_worktree_list(
|
|
649
|
+
self, app, sample_repo, task_manager, monkeypatch
|
|
650
|
+
):
|
|
651
|
+
"""Enter on the worktree list triggers open_shell (was swallowed by select)."""
|
|
652
|
+
repo_path, branch = sample_repo
|
|
653
|
+
task_manager.create_task("FOOTER-ENTER", ["sample-repo"], branch)
|
|
654
|
+
|
|
655
|
+
called = []
|
|
656
|
+
monkeypatch.setattr(app, "action_open_shell", lambda: called.append(True))
|
|
657
|
+
|
|
658
|
+
async with app.run_test() as pilot:
|
|
659
|
+
await pilot.pause()
|
|
660
|
+
await pilot.press("tab")
|
|
661
|
+
await pilot.pause()
|
|
662
|
+
await pilot.press("enter")
|
|
663
|
+
await pilot.pause()
|
|
664
|
+
|
|
665
|
+
assert called
|
|
File without changes
|
{tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/README.md
RENAMED
|
File without changes
|
{tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/dashboard_app.py
RENAMED
|
File without changes
|
{tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/data_viewer.py
RENAMED
|
File without changes
|
{tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/todo_app.py
RENAMED
|
File without changes
|
{tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/assets/worker_demo.py
RENAMED
|
File without changes
|
{tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/references/layouts.md
RENAMED
|
File without changes
|
|
File without changes
|
{tasktree_manager-2.3.0 → tasktree_manager-2.4.0}/.claude/skills/textual-tui/references/styling.md
RENAMED
|
File without changes
|
{tasktree_manager-2.3.0 → tasktree_manager-2.4.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
|
|
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.3.0 → tasktree_manager-2.4.0}/tasktree_manager/widgets/messages_panel.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
|