taskai-cli 1.8.0__tar.gz → 1.9.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.
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/DEVLOG.md +115 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/DEVPLAN.md +210 -23
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/PKG-INFO +1 -1
- taskai_cli-1.9.0/docs/browser-mode.md +51 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/getting-started.md +9 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/index.md +2 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/interactive-mode.md +2 -1
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/managing-the-tree.md +16 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/pyproject.toml +1 -1
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/browser.py +21 -2
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/cli.py +76 -5
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/help_menu.py +23 -2
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/json_dir_database.py +85 -4
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/models.py +4 -0
- taskai_cli-1.9.0/taskai/static/camera.js +208 -0
- taskai_cli-1.9.0/taskai/static/canvas.js +138 -0
- taskai_cli-1.9.0/taskai/static/config.js +79 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/static/console.js +38 -42
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/static/editpanel.js +7 -18
- taskai_cli-1.9.0/taskai/static/graph.js +196 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/static/index.html +5 -0
- taskai_cli-1.9.0/taskai/static/navigation.js +44 -0
- taskai_cli-1.9.0/taskai/static/render.js +285 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/static/shortcuts.js +38 -23
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/views.py +10 -0
- taskai_cli-1.8.0/docs/browser-mode.md +0 -29
- taskai_cli-1.8.0/taskai/static/canvas.js +0 -816
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/.github/workflows/publish-to-pypi.yml +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/.gitignore +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/.readthedocs.yaml +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/README.md +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/TRIAGE.md +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/.gitignore +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/_static/.gitkeep +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/ai-integration.md +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/commands.md +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/conf.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/configuration.md +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/item-data.md +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/docs/requirements.txt +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/scripts/bump_version.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/config.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/errors.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/filters.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/llm_models.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/services/ai.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/services/pomodoro.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/services/repair_database.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/services/user_setup.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/taskai/static/style.css +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/test/test_cli.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/test/test_execution.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/test/test_json_dir_database.py +0 -0
- {taskai_cli-1.8.0 → taskai_cli-1.9.0}/test/test_view.py +0 -0
|
@@ -1,3 +1,118 @@
|
|
|
1
|
+
# 9-14
|
|
2
|
+
|
|
3
|
+
Gave the browser DAG chain support (backend chain fields already existed,
|
|
4
|
+
frontend didn't know about them) and cleaned up `canvas.js` along the way.
|
|
5
|
+
Durable versions in DEVPLAN 1.8 (chains) and Phase 3 (frontend cleanup).
|
|
6
|
+
|
|
7
|
+
- **Layout rewrite.** Bottom-up subtree-width algorithm replaces the old
|
|
8
|
+
fixed-grid layout — a chain renders as a straight line, its real children
|
|
9
|
+
push into a row to the right instead of centering below it. Found and
|
|
10
|
+
fixed a real overlap bug from a screenshot: a chain node's footprint is
|
|
11
|
+
lopsided (all reach to the right), so layout now tracks separate
|
|
12
|
+
`left`/`right` extents per node instead of one symmetric width.
|
|
13
|
+
- **Navigation.** Arrow keys are pure tree nav (chain or not); `f`/`b`
|
|
14
|
+
(and `forward`/`back` from the console) are a separate chain axis along
|
|
15
|
+
`chainNext`/`chainPrev`. Split out after realizing one direction can't
|
|
16
|
+
mean "sibling," "child," and "next in chain" at once for a node that's
|
|
17
|
+
both a chain head and has its own tree children.
|
|
18
|
+
- **Frontend split.** `canvas.js` (one file, ~900 lines) is now
|
|
19
|
+
`config.js`/`graph.js`/`camera.js`/`navigation.js`/`render.js` plus a
|
|
20
|
+
slim `canvas.js` holding a `state` object and the event wiring — the
|
|
21
|
+
three panel scripts (`console.js`/`editpanel.js`/`shortcuts.js`) updated
|
|
22
|
+
to match.
|
|
23
|
+
|
|
24
|
+
# 9-10
|
|
25
|
+
|
|
26
|
+
Browser navigation polish, then a new feature: attribute filters for `task
|
|
27
|
+
show`. Durable versions in DEVPLAN 1.7 (nav) and Phase 5 (filters).
|
|
28
|
+
|
|
29
|
+
## Canvas navigation (`canvas.js`)
|
|
30
|
+
|
|
31
|
+
- **Arrow left/right walks the whole depth level, not just direct siblings.**
|
|
32
|
+
The layout gives every node at a given depth the same `y`, so the "level" is
|
|
33
|
+
just `nodes` sharing `selectedNode.y` sorted by `x` — no explicit depth
|
|
34
|
+
bookkeeping. `→` off the last child of a parent now crosses into the next
|
|
35
|
+
cousin subtree instead of wrapping; wrap happens only at the ends of the
|
|
36
|
+
level (right off the last → level's first node, left off the first → last).
|
|
37
|
+
- **Arrow up/down has per-parent memory.** `lastChildByParent` (a plain
|
|
38
|
+
`parentId → childId` map, ids not refs since nodes are rebuilt each
|
|
39
|
+
`applyTree`) records every arrow move against its parent; `down` returns to
|
|
40
|
+
the last child visited under that node, falling back to `children[0]`. Also
|
|
41
|
+
keyed for the synthetic `__root__`, so `down` from the top re-enters the
|
|
42
|
+
root subtree you were last in. Stale ids just miss and fall back.
|
|
43
|
+
- **Removed up/down wrap-around.** `down` from a leaf and `up` from the top
|
|
44
|
+
now do nothing (were: wrap to rootNode / dive to the deepest leaf).
|
|
45
|
+
Left/right still wrap within a level.
|
|
46
|
+
- **`focusOnNode` recomputes its target every frame** from the current
|
|
47
|
+
`canvas.width/height` instead of once up front. A side panel opening in
|
|
48
|
+
step with the ease (the `a` shortcut, closing the edit panel) resizes the
|
|
49
|
+
canvas mid-animation; a target captured once left the node cut off at the
|
|
50
|
+
edge.
|
|
51
|
+
- **`fitAll` (show all / `0`) clamps zoom-in to `STYLE.zoom.focusScale`**
|
|
52
|
+
(0.85). A filtered `show` on a handful of nodes was blowing them up to fill
|
|
53
|
+
the screen; the user can still wheel in past that.
|
|
54
|
+
- **Double-click a node opens the edit panel** (+ selects + eases to it), not
|
|
55
|
+
just center/zoom.
|
|
56
|
+
|
|
57
|
+
## Edit panel (`editpanel.js`) + shortcuts (`shortcuts.js`)
|
|
58
|
+
|
|
59
|
+
- **`Enter` on a single-line field** (name/status/priority/due_by) flushes the
|
|
60
|
+
1s debounce immediately and blurs — the edit is done. `handleFieldChange`
|
|
61
|
+
got an `immediate` flag; textarea and checkbox are excluded.
|
|
62
|
+
- **`a` (add child): open the edit panel before `focusOnNode`** so the ease
|
|
63
|
+
tracks the narrowing canvas and lands centred (pairs with the per-frame
|
|
64
|
+
target change above).
|
|
65
|
+
- **`Esc` no longer moves the camera.** Was `showAll()` → `fitAll()`. Now:
|
|
66
|
+
leave a focused field → close the edit panel (with `focusOnNode` on the
|
|
67
|
+
selection so it re-centres as the canvas widens) → clear the selection.
|
|
68
|
+
Closing the panel with `e` routes through the same
|
|
69
|
+
`closeEditPanelAndRefocus()` helper, so `e` and `Esc` behave identically.
|
|
70
|
+
- **Backtick when the console is open but unfocused → focus the input**
|
|
71
|
+
instead of toggling it closed. Also bound `~`.
|
|
72
|
+
- **`Delete` shortcut: no more `confirm()` dialog** — it broke the flow.
|
|
73
|
+
|
|
74
|
+
## `task show` attribute filters — new `taskai/filters.py`
|
|
75
|
+
|
|
76
|
+
task show 'status=IN PROGRESS' 'priority>=2' 'due_by<12-31-2026'
|
|
77
|
+
|
|
78
|
+
Implemented logic → CLI → browser, in that order.
|
|
79
|
+
|
|
80
|
+
- **Logic** (`filters.py`): each arg is one `attr<op>value` test, AND-ed. Ops
|
|
81
|
+
`= > < >= <=`. `=` on a text attr (`name`/`description`/`status`) is an
|
|
82
|
+
fnmatch; `priority`/`id`/`parent_id` → int, `due_by`/`created_on` → date
|
|
83
|
+
(`MM-DD-YYYY`, time dropped), `completed` → bool. `None` never satisfies an
|
|
84
|
+
ordering op. `looks_like_filters(tokens)` decides filter-query vs. plain
|
|
85
|
+
id/name target (structural: known attr + operator). `match_items(db, exprs)`
|
|
86
|
+
→ matching ids; `with_ancestors(db, ids)` pads with every match's parent
|
|
87
|
+
chain so the result is a set of complete root-to-node paths. `FilterError`
|
|
88
|
+
(a `ValueError`) for a bad token/value.
|
|
89
|
+
- **Renderer** (`views.py`): `view_lists` gained `only_ids` — `_recursive_print`
|
|
90
|
+
and linked-item display skip anything not in the set. Ancestors are always
|
|
91
|
+
in the set, so the pruned walk stays a contiguous tree.
|
|
92
|
+
- **CLI** (`cli.py`): `Controller.show_filtered(tokens)` — parse, match,
|
|
93
|
+
expand, render pruned, print `"N items matched"`. `show` dispatch checks
|
|
94
|
+
`looks_like_filters` first, else the old `all` / id / name path. Fixed a
|
|
95
|
+
latent `IndexError` on bare `task show` in passing (`args[1]` with no
|
|
96
|
+
guard). Documented in `help_menu.py` (also the AI's command reference) and
|
|
97
|
+
the `docs/` prose pages (`managing-the-tree`, `getting-started`, `index`,
|
|
98
|
+
`interactive-mode`, `browser-mode`); `commands.md` tracks `help_general`
|
|
99
|
+
automatically.
|
|
100
|
+
- **Browser** (`browser.py` + `console.js`): `_run_show` routes a filter query
|
|
101
|
+
to `_run_filter` → a pruned tree (`_filtered_tree` narrows each item's
|
|
102
|
+
`child_ids`/`linked_ids` to the kept set so the canvas never walks into a
|
|
103
|
+
removed node) + a `filtered` flag. `console.js`'s `show` handler sees the
|
|
104
|
+
flag, fits the view, and stores the raw filter args in `activeFilter`.
|
|
105
|
+
- **Filter persistence.** `CommandRequest` gained an optional `filter` string;
|
|
106
|
+
`_response_tree()` re-prunes every mutation's returned tree to it. `console.js`
|
|
107
|
+
attaches `activeFilter` to every `/api/command` POST (via `postCommand`,
|
|
108
|
+
which `sendFieldUpdate`/`sendComment` now also use). So a filtered view
|
|
109
|
+
survives delete / done / field edits — it was snapping back to the whole
|
|
110
|
+
forest before. `show all` / navigating to a specific node clears it. Known
|
|
111
|
+
gap: `a` (add child) under an active filter creates the item but it won't
|
|
112
|
+
match the filter, so it doesn't appear or get selected.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
1
116
|
# 9-3
|
|
2
117
|
|
|
3
118
|
Browser session — console-driven navigation and selection, mostly beyond the
|
|
@@ -60,7 +60,14 @@ opening a second connection.
|
|
|
60
60
|
|
|
61
61
|
**Views.** [taskai/views.py](taskai/views.py): Rich-based tree rendering
|
|
62
62
|
(`view_lists`, `view_item`), display format configurable via
|
|
63
|
-
`DISPLAY_STRING`/`DISPLAY_COLORS`.
|
|
63
|
+
`DISPLAY_STRING`/`DISPLAY_COLORS`. `view_lists` takes an optional `only_ids`
|
|
64
|
+
set to prune the walk (used by `task show`'s filters).
|
|
65
|
+
|
|
66
|
+
**Filters.** [taskai/filters.py](taskai/filters.py): parsing + evaluation for
|
|
67
|
+
`task show 'attr<op>value' ...` — `looks_like_filters` / `parse_filters`
|
|
68
|
+
(→ `FilterExpr`, `FilterError`), `match_items(db, exprs)`, and
|
|
69
|
+
`with_ancestors(db, ids)` which pads a match set out with parent chains so it
|
|
70
|
+
renders as a coherent tree. Pure; imported by both `cli.py` and `browser.py`.
|
|
64
71
|
|
|
65
72
|
**AI services.** [taskai/services/ai.py](taskai/services/ai.py):
|
|
66
73
|
`ai_headstart_service` (single item → LLM suggests a next concrete step,
|
|
@@ -404,14 +411,26 @@ the original checklist.
|
|
|
404
411
|
`edit <id|name>`, mirroring the canvas's single-click vs. double-click
|
|
405
412
|
split.
|
|
406
413
|
- [x] **Tree navigation** — `navigate(direction)` in `canvas.js`, wired to
|
|
407
|
-
`up` / `down` / `left` / `right` console commands
|
|
408
|
-
`parent` back-reference (set in `buildTree` / `applyTree`)
|
|
409
|
-
goes up as well as down
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
414
|
+
`up` / `down` / `left` / `right` console commands and the arrow keys.
|
|
415
|
+
Nodes carry a `parent` back-reference (set in `buildTree` / `applyTree`)
|
|
416
|
+
so the walk goes up as well as down. Each move behaves like a `show
|
|
417
|
+
<target>` (select + ease/zoom; `fitAll` when the target is `rootNode`).
|
|
418
|
+
Reworked 9-10:
|
|
419
|
+
- `up` → parent, `down` → a child. **No wrap** — `down` from a leaf and
|
|
420
|
+
`up` from the top do nothing (was: loop to rootNode / dive to the
|
|
421
|
+
deepest leaf).
|
|
422
|
+
- `down` consults **`lastChildByParent`** (a `parentId → childId` map
|
|
423
|
+
every arrow move updates) and returns to the last child visited under
|
|
424
|
+
that node, else `children[0]`. Also keyed for `__root__`.
|
|
425
|
+
- `left`/`right` step through the **whole depth level**, not just direct
|
|
426
|
+
siblings — the level is `nodes` sharing `selectedNode.y` (the layout
|
|
427
|
+
gives one `y` per depth) sorted by `x`, so `→` crosses into a cousin
|
|
428
|
+
subtree. Wrap only at the ends of the level.
|
|
429
|
+
- `focusOnNode` recomputes its target every frame from the live canvas
|
|
430
|
+
size, so an ease that overlaps a panel width animation still lands
|
|
431
|
+
centred. `fitAll` caps zoom-in at `STYLE.zoom.focusScale`.
|
|
432
|
+
- Double-click a node also opens the edit panel now; `Esc` no longer
|
|
433
|
+
moves the camera (leave field → close panel + re-centre → deselect).
|
|
415
434
|
- [x] **`.` = the selected node** (`resolveDotRefs` in `console.js`). A bare
|
|
416
435
|
`.` token in any console command is swapped for `selectedNode.id`
|
|
417
436
|
before dispatch, so `update . --priority 3`, `done .`, `edit .` act on
|
|
@@ -422,6 +441,36 @@ the original checklist.
|
|
|
422
441
|
collapsing the right panel and restoring canvas width. Complements the
|
|
423
442
|
existing `edit <id|name>` that opens it.
|
|
424
443
|
|
|
444
|
+
### 1.8 — Chain rendering & navigation (post-plan, beyond original scope)
|
|
445
|
+
|
|
446
|
+
The backend chain fields (`is_chain_head`/`prev_chain_id`/`next_chain_id`,
|
|
447
|
+
`task next`) existed with no browser support at all; this gives the DAG a
|
|
448
|
+
view onto them.
|
|
449
|
+
|
|
450
|
+
- [x] **Layout rewrite.** The old depth-first "leaf counter" layout
|
|
451
|
+
(`canvas.js`) is now a bottom-up subtree-width algorithm (`graph.js`,
|
|
452
|
+
`measure`/`place`) — every node's footprint is the sum of its
|
|
453
|
+
children's real footprints instead of a fixed grid, so sibling
|
|
454
|
+
subtrees pack at minimum distance instead of getting a fixed spacing
|
|
455
|
+
regardless of content.
|
|
456
|
+
- [x] **Chains render as a straight line.** A chain node's real children
|
|
457
|
+
(`child_ids`) push into a row to its right instead of centering below
|
|
458
|
+
it; its chain successor (`chainNext`) drops straight down, waiting
|
|
459
|
+
until that side row's height clears so nothing overlaps. Fixed a real
|
|
460
|
+
bug found via a screenshot: a chain node's footprint is lopsided (all
|
|
461
|
+
reach is to the right, none to the left), so layout tracks separate
|
|
462
|
+
`left`/`right` extents per node instead of one symmetric width —
|
|
463
|
+
otherwise a deep chain could bleed into an unrelated cousin subtree.
|
|
464
|
+
Chain edges render as bold arrows (`render.js`, `drawChainEdges`),
|
|
465
|
+
distinct from plain tree edges and dashed soft-link edges.
|
|
466
|
+
- [x] **Navigation.** Arrow keys stay pure tree navigation (up/down =
|
|
467
|
+
parent/children, left/right = the depth level) — a chain node's real
|
|
468
|
+
children are still its tree children for this purpose, they just
|
|
469
|
+
render to the side. `f`/`b` (`navigation.js`, `shortcuts.js`) are a
|
|
470
|
+
separate chain axis: forward/back along `chainNext`/`chainPrev`,
|
|
471
|
+
independent of tree position. Also usable from the console as
|
|
472
|
+
`forward`/`back`.
|
|
473
|
+
|
|
425
474
|
---
|
|
426
475
|
|
|
427
476
|
## Phase 2 — AI tools
|
|
@@ -580,11 +629,17 @@ Re-scope this section before starting 2.4.
|
|
|
580
629
|
|
|
581
630
|
## Phase 3 — Polish
|
|
582
631
|
|
|
583
|
-
- [
|
|
584
|
-
|
|
585
|
-
`
|
|
586
|
-
|
|
587
|
-
|
|
632
|
+
- [x] **Frontend cleanup.** `canvas.js` split into `config.js` (`STYLE`),
|
|
633
|
+
`graph.js` (`Graph` class — tree/chain construction + layout),
|
|
634
|
+
`camera.js` (`Camera` class — pan/zoom/panel-width transform, never
|
|
635
|
+
holds a `Graph`, takes one as an argument where it needs one),
|
|
636
|
+
`navigation.js` (`navigateGraph`, pure aside from its own "last child
|
|
637
|
+
visited" memory), and `render.js` (drawing only). `canvas.js` itself is
|
|
638
|
+
now just the `state` object (`{graph, camera, selectedNode,
|
|
639
|
+
hoveredNode}`) plus DOM event wiring — the readable top-level flow.
|
|
640
|
+
`console.js`/`editpanel.js`/`shortcuts.js` read and act through `state`
|
|
641
|
+
instead of the old flat globals. Still no bundler — six `<script>` tags
|
|
642
|
+
loaded in dependency order.
|
|
588
643
|
- [~] **Docs.** README is synced with `execute_commands` (command list,
|
|
589
644
|
`task ai` flags, Web UI section, `task setup` walkthrough);
|
|
590
645
|
`help_menu.py` lists `task browser` / `task help`, and its stale
|
|
@@ -616,10 +671,10 @@ Re-scope this section before starting 2.4.
|
|
|
616
671
|
needs no edit — it `literalinclude`s the `builder-inited`-generated
|
|
617
672
|
dump of `help_general`.
|
|
618
673
|
- [ ] **UX pass.** Empty/loading state for the DAG when a user has no tasks
|
|
619
|
-
yet (currently the app would just render nothing);
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
link edges, now deferred to 2.0.)
|
|
674
|
+
yet (currently the app would just render nothing); consistent
|
|
675
|
+
spacing/typography in `style.css`. (Console-toggle keybind — done in
|
|
676
|
+
Phase 4's `shortcuts.js` (backtick). Edge legend dropped — depended on
|
|
677
|
+
1.4's dependency/link edges, now deferred to 2.0.)
|
|
623
678
|
- [ ] Re-check the Phase 0 fixes are still holding once the web UI is
|
|
624
679
|
exercising more command paths than the CLI alone did.
|
|
625
680
|
|
|
@@ -715,16 +770,30 @@ endpoints.
|
|
|
715
770
|
**`console.js` change:** extracted `toggleConsole(force)` (focus on
|
|
716
771
|
open / blur on close), `showAll()`, and a `postCommand(input)` helper,
|
|
717
772
|
all reused by `shortcuts.js`.
|
|
773
|
+
**Refinements (9-10):** `Delete` lost its `confirm()` (broke the flow);
|
|
774
|
+
`Esc` no longer moves the camera (leave field → close edit panel with a
|
|
775
|
+
re-centre → deselect), and `e` closing the panel shares that path;
|
|
776
|
+
backtick focuses the console input when the console is open but
|
|
777
|
+
unfocused rather than toggling it shut; double-click a node opens the
|
|
778
|
+
edit panel; `Enter` in a single-line edit-panel field commits + blurs.
|
|
779
|
+
`sendFieldUpdate` / `sendComment` moved onto `postCommand`, which now
|
|
780
|
+
also carries the active `show` filter (see Phase 5).
|
|
718
781
|
|
|
719
782
|
---
|
|
720
783
|
|
|
721
784
|
## Phase 5 — CLI view
|
|
722
785
|
|
|
723
|
-
- [
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
786
|
+
- [~] **Expand what `task show` renders, and its options.** Attribute
|
|
787
|
+
**filtering landed early** (9-10), out of band, driven by a concrete
|
|
788
|
+
ask: `task show 'attr<op>value' ...` in `taskai/filters.py` —
|
|
789
|
+
AND-ed tests, ops `= > < >= <=`, `=` on text is fnmatch, matches shown
|
|
790
|
+
with their parent chain (`view_lists` gained `only_ids`). Wired through
|
|
791
|
+
the CLI (`Controller.show_filtered`, `show` dispatch) and the browser
|
|
792
|
+
(`_run_show` → `_run_filter` → pruned tree + `filtered` flag, plus a
|
|
793
|
+
persisted `activeFilter` on `/api/command` so mutations keep the view).
|
|
794
|
+
Still open, and still needs a session with Alex to scope: depth limits,
|
|
795
|
+
flat vs. tree layout, sort order, inline comments / links, format and
|
|
796
|
+
colour toggles.
|
|
728
797
|
- [ ] **New view-settings config keys.** Extend `CLIConfig` (today just
|
|
729
798
|
`DISPLAY_STRING` / `DISPLAY_COLORS`) with keys backing whatever the
|
|
730
799
|
expanded view supports; document them in `docs/configuration.md` and
|
|
@@ -757,3 +826,121 @@ endpoints.
|
|
|
757
826
|
- [ ] Write a short "show and tell" (what it is, the everything-is-an-item
|
|
758
827
|
model, the AI layer, the canvas view) built around the gif, and decide
|
|
759
828
|
on channels (Show HN, r/commandline, r/productivity, GitHub topics).
|
|
829
|
+
|
|
830
|
+
---
|
|
831
|
+
|
|
832
|
+
## Phase 8 — Chain CLI & data integrity
|
|
833
|
+
|
|
834
|
+
The chain data model (`is_chain_head`/`prev_chain_id`/`next_chain_id` on
|
|
835
|
+
`TodoItem`, `JsonDirectoryDatabase.insert_node_into_chain` /
|
|
836
|
+
`remove_node_from_chain` / `delete_chain`) and the browser rendering/nav for
|
|
837
|
+
it (DEVPLAN 1.8) already exist. This phase is CLI-side: finish the command
|
|
838
|
+
surface and fix the bugs found while designing it, so a chain is a fully
|
|
839
|
+
first-class thing a user can build, browse, and tear down from the terminal
|
|
840
|
+
— blocking the next release. Intended use cases: **(1)** define a sequential
|
|
841
|
+
chain of tasks, **(2)** delete individual items out of a chain — linked-list
|
|
842
|
+
style, the rest re-links around the hole — and **(3)** delete an entire
|
|
843
|
+
chain in one shot. A fourth, explicitly called out as a primary workflow:
|
|
844
|
+
navigate along an in-progress chain and `clear` away the items already
|
|
845
|
+
marked done as you go, tetris-style, leaving just the part still in
|
|
846
|
+
progress.
|
|
847
|
+
|
|
848
|
+
**Bugs to fix (foundational — the new commands below depend on these):**
|
|
849
|
+
|
|
850
|
+
- [x] **Deleting a chain head orphans the rest of the chain.**
|
|
851
|
+
`remove_node_from_chain`'s "node has only a next" branch (fires
|
|
852
|
+
whenever the item being removed from a chain is currently the head)
|
|
853
|
+
promotes the next link to head and copies the old head's `parent_id`
|
|
854
|
+
onto it, but never updates the actual parent's `child_ids` to swap the
|
|
855
|
+
deleted head's id for the new one. The parent keeps pointing at a
|
|
856
|
+
dead id; the rest of the chain becomes unreachable from the tree
|
|
857
|
+
(not anyone's child, not a root) even though the JSON records
|
|
858
|
+
survive. Fix this at the `remove_node_from_chain`/`delete_item` level
|
|
859
|
+
itself (not as a special case bolted onto one call site) — `clear`'s
|
|
860
|
+
item-by-item deletion loop and a plain `task delete <head>` both need
|
|
861
|
+
to go through this same path and come out correct.
|
|
862
|
+
- [x] **`_get_root_ids` (`cli.py`) doesn't exclude chain members.** It
|
|
863
|
+
filters on `parent_id is None` only, so `task show all` lists every
|
|
864
|
+
non-head chain link as a spurious top-level root. Add the same
|
|
865
|
+
`prev_chain_id is None` check the browser's `Graph` already uses.
|
|
866
|
+
- [x] **`insert_node_into_chain` doesn't detach `node` from a chain it's
|
|
867
|
+
already in**, and doesn't clear a stale `is_chain_head` on it. Not
|
|
868
|
+
hit today because the only caller (`next_node`) always hands it a
|
|
869
|
+
freshly-created item — becomes a real risk once `task chain` (below)
|
|
870
|
+
lets you splice two *existing* items together, possibly ones already
|
|
871
|
+
mid-chain.
|
|
872
|
+
|
|
873
|
+
**New/wired CLI commands:**
|
|
874
|
+
|
|
875
|
+
- [x] `task next <prev> <name>` — already exists (create + append), keep as
|
|
876
|
+
is. Fix in passing: `execute_commands`'s `case "next":` doesn't
|
|
877
|
+
forward `**kwargs` to `Controller.next_node`, so e.g. `--due_by` on
|
|
878
|
+
`task next` is silently dropped — same landmine class as the old
|
|
879
|
+
`task ai --context` bug (DEVLOG 8-22). Thread `**kwargs` through like
|
|
880
|
+
`case "ai":` was fixed to do.
|
|
881
|
+
- [x] **`task chain <prev_id> <node_id>`** — new dispatch wiring the
|
|
882
|
+
already-written, currently-unreachable `Controller.insert_node_as_chain`.
|
|
883
|
+
Links two *existing* items. Per Alex: **do not refuse if `node`
|
|
884
|
+
already has a parent — pop it off that parent (remove from
|
|
885
|
+
`child_ids`, clear `parent_id`) and splice it into the chain
|
|
886
|
+
instead.** This is the "malleable graph" call: moving a node from
|
|
887
|
+
tree-child to chain-member is one command, not detach-then-chain.
|
|
888
|
+
`node`'s own `child_ids` are untouched either way — a chain member is
|
|
889
|
+
allowed real children of its own (they render to the side; see
|
|
890
|
+
DEVPLAN 1.8), only its *parent* relationship is exclusive between
|
|
891
|
+
"tree child" and "chain member."
|
|
892
|
+
- [x] **`task unchain <node_id>`** — new dispatch wiring the
|
|
893
|
+
already-written, currently-unreachable `Controller.remove_node_as_chain`.
|
|
894
|
+
Detaches one item from its chain without deleting it. Per Alex:
|
|
895
|
+
**symmetric to `chain` above** — after splicing `node` out
|
|
896
|
+
(`remove_node_from_chain`), add it as a child of *the chain head's
|
|
897
|
+
parent* (walk `prev_chain_id` to the true head, read that head's
|
|
898
|
+
`parent_id`, `add_child_to_parent` there; if the head is itself a
|
|
899
|
+
root, `node` becomes a root too). Pulls it out of the sequence and
|
|
900
|
+
into the parent's set of children. A no-op on the reparenting step if
|
|
901
|
+
`node` being unchained *is* the head — it's already that parent's
|
|
902
|
+
child.
|
|
903
|
+
- [x] **`task delete <id> -chain`** — new flag on the existing `delete`
|
|
904
|
+
command (same convention as `complete`/`done`'s `-r`/`-recursive`),
|
|
905
|
+
wiring the already-written, currently-unreachable `db.delete_chain`.
|
|
906
|
+
Deletes the *entire* chain `id` belongs to, not just `id` onward —
|
|
907
|
+
walk `prev_chain_id` back to the true head first, so it works no
|
|
908
|
+
matter which link you target, then cascade forward and detach the
|
|
909
|
+
head from its tree parent the normal way.
|
|
910
|
+
- [x] **`clear` needs to actually reach chain members.**
|
|
911
|
+
`Controller._flatten_item_descendants` (used by `task clear <parent>`
|
|
912
|
+
to scope which completed items to sweep) walks `child_ids` only. A
|
|
913
|
+
chain's non-head members are never in anyone's `child_ids` — they're
|
|
914
|
+
only reachable by walking `next_chain_id` from the head — so today
|
|
915
|
+
`task clear <chain-head-or-ancestor>` silently misses every
|
|
916
|
+
completed item past the head. Extend the walk to also follow
|
|
917
|
+
`next_chain_id` (and recurse into each chain member's own
|
|
918
|
+
`child_ids`, same as the browser's `flatten`/`buildTree` do). Bare
|
|
919
|
+
`task clear` (no parent) already iterates every item id in the db, so
|
|
920
|
+
it isn't blind to chain members the same way — it just needs the
|
|
921
|
+
head-deletion bug fixed above so clearing a *done* head doesn't
|
|
922
|
+
orphan the still-in-progress rest of the chain out from under itself.
|
|
923
|
+
This is the primary use case Alex called out: navigate along a chain,
|
|
924
|
+
`clear` the done items as you pass them, tetris-style, and be left
|
|
925
|
+
with just the in-progress remainder correctly re-linked (and, if the
|
|
926
|
+
head itself was cleared, correctly reattached to the tree parent).
|
|
927
|
+
|
|
928
|
+
**Data structuring:**
|
|
929
|
+
|
|
930
|
+
- [x] **Drop the stored `is_chain_head` field, derive it instead**
|
|
931
|
+
(`prev_chain_id is None and next_chain_id is not None`). It's exactly
|
|
932
|
+
the field that's gone out of sync in the bugs above, the frontend
|
|
933
|
+
already doesn't use it (uses `chainNext`/`chainPrev` presence
|
|
934
|
+
instead — DEVPLAN 1.8), and removing it is backward-compatible
|
|
935
|
+
(Pydantic silently ignores the leftover key in old JSON — no
|
|
936
|
+
migration needed). Replace the one real remaining consumer,
|
|
937
|
+
`delete_item`'s cascade check
|
|
938
|
+
(`get_item_attr(child_id, "is_chain_head")`), with the derived check.
|
|
939
|
+
|
|
940
|
+
**Docs:** [x] `next`/`chain`/`unchain`/`delete -chain` are now documented in
|
|
941
|
+
[taskai/help_menu.py](taskai/help_menu.py) (a new "Chains:" section, an
|
|
942
|
+
updated "Deleting:" section, and a worked example) — which is also
|
|
943
|
+
`task ai`'s verbatim command reference, so the AI can use all of this too
|
|
944
|
+
now.
|
|
945
|
+
|
|
946
|
+
Phase 8 is complete.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Browser mode
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
task browser
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
This starts a local web server and serves a visual view of your task tree.
|
|
8
|
+
|
|
9
|
+
## The canvas
|
|
10
|
+
|
|
11
|
+
The whole tree is drawn as connected cards. Drag to pan, scroll to zoom.
|
|
12
|
+
Double-click a card to center on it and open its edit panel.
|
|
13
|
+
|
|
14
|
+
- **Completed** items are tinted green.
|
|
15
|
+
- **`status`** text appears in the top corner of a card.
|
|
16
|
+
- **Soft links** (`task link`) are drawn as a dashed copy of the linked item
|
|
17
|
+
under its host, distinct from the solid parent/child connectors.
|
|
18
|
+
|
|
19
|
+
## The edit panel
|
|
20
|
+
|
|
21
|
+
Click a card to select it; double-click it, or press `e`, to open the edit
|
|
22
|
+
panel on the right. It has a field for the name, description, status, priority,
|
|
23
|
+
due date, and completion, plus a comment feed. Changes are saved as you make
|
|
24
|
+
them — there is no save button. In a single-line field, `Enter` commits the
|
|
25
|
+
change and leaves the field.
|
|
26
|
+
|
|
27
|
+
## The console
|
|
28
|
+
|
|
29
|
+
The panel at the bottom takes raw `task` commands and runs them against the
|
|
30
|
+
same data, printing their output and refreshing the canvas. Anything you can
|
|
31
|
+
type on the command line works here.
|
|
32
|
+
|
|
33
|
+
Filtering sticks: after `show 'priority>=2'` the canvas shows only the matches
|
|
34
|
+
(and their parents), and it stays scoped that way as you edit and delete —
|
|
35
|
+
until `show all` clears it.
|
|
36
|
+
|
|
37
|
+
## Keyboard
|
|
38
|
+
|
|
39
|
+
Press `?` for the full list in the app. The essentials:
|
|
40
|
+
|
|
41
|
+
| Key | Action |
|
|
42
|
+
|---|---|
|
|
43
|
+
| arrows | move the selection through the tree |
|
|
44
|
+
| `Shift`+arrows | pan the view |
|
|
45
|
+
| `+` / `-` / `0` | zoom in / out / fit |
|
|
46
|
+
| `e` | toggle the edit panel |
|
|
47
|
+
| `` ` `` | focus / close the console |
|
|
48
|
+
| `a` | add a child of the selected card and name it |
|
|
49
|
+
| `d` | toggle done on the selected card |
|
|
50
|
+
| `Delete` | delete the selected card |
|
|
51
|
+
| `Esc` | leave a field, then close the panel, then clear the selection |
|
|
@@ -82,6 +82,15 @@ task show "Write first post"
|
|
|
82
82
|
task show 2
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
Or narrow the tree to items matching a set of attribute tests (each shown with
|
|
86
|
+
its parents for context):
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
task show 'priority>=2' 'completed=false'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
See [Managing the tree](managing-the-tree.md) for the operators.
|
|
93
|
+
|
|
85
94
|
## Work an item and finish it
|
|
86
95
|
|
|
87
96
|
```bash
|
|
@@ -13,6 +13,8 @@ A command-line task manager with AI features.
|
|
|
13
13
|
items at any depth; there's no separate notion of "lists"
|
|
14
14
|
- **Attach data to each item** — description, due date, priority, status,
|
|
15
15
|
completion, comments
|
|
16
|
+
- **Filter** — `task show 'priority>=2' 'due_by<10-01-2026'` narrows the tree to
|
|
17
|
+
matching items and their parents
|
|
16
18
|
- **Soft-link** an item under more than one parent without moving it
|
|
17
19
|
- **Interactive mode** — run `task` with no arguments for a live view that
|
|
18
20
|
redraws as you type commands
|
|
@@ -18,6 +18,7 @@ redraws with the updated tree.
|
|
|
18
18
|
|
|
19
19
|
- A `show` command sets the view that stays on screen — `task show "Launch
|
|
20
20
|
blog"` keeps that subtree visible as you keep working; `show all` goes back to
|
|
21
|
-
the whole tree.
|
|
21
|
+
the whole tree. A [filter](managing-the-tree.md) such as `show 'priority>=2'`
|
|
22
|
+
works here too, and stays applied as the view redraws.
|
|
22
23
|
- Every other command runs, then the view refreshes.
|
|
23
24
|
- Leave with `exit`, `quit`, or Ctrl-C.
|
|
@@ -32,6 +32,22 @@ task show 4 # one item in full, by id
|
|
|
32
32
|
task show "Set up hosting" # one item in full, by name
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## Filter the tree
|
|
36
|
+
|
|
37
|
+
Give `task show` one or more `attribute<operator>value` tests and it prints only
|
|
38
|
+
the items matching *all* of them, each with its parent chain for context:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
task show 'name=Write*' # = on text is a glob match
|
|
42
|
+
task show 'priority>=2' 'completed=false'
|
|
43
|
+
task show 'status=in review' 'due_by<10-01-2026'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Operators are `=`, `>`, `<`, `>=`, `<=`. Text fields (`name`, `description`,
|
|
47
|
+
`status`) glob-match on `=`; `priority`, `due_by`, `created_on`, and `id`
|
|
48
|
+
compare numerically / by date. Put no space around the operator, and quote each
|
|
49
|
+
test so your shell doesn't treat `>` and `<` as redirects.
|
|
50
|
+
|
|
35
51
|
## Move items
|
|
36
52
|
|
|
37
53
|
```bash
|
|
@@ -81,6 +81,25 @@ def get_tree():
|
|
|
81
81
|
|
|
82
82
|
class CommandRequest(BaseModel):
|
|
83
83
|
input: str
|
|
84
|
+
# raw filter args from the last `show <attr><op>value ...` the frontend
|
|
85
|
+
# ran, if any — so a mutation's refreshed tree stays scoped to that view
|
|
86
|
+
# instead of snapping back to the whole forest
|
|
87
|
+
filter: str | None = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _response_tree(filter_str):
|
|
91
|
+
"""The tree a mutation should return: pruned to `filter_str`'s matches
|
|
92
|
+
(+ their parent chains) when one is active, else the whole forest."""
|
|
93
|
+
if not filter_str:
|
|
94
|
+
return _full_tree()
|
|
95
|
+
tokens = _parse_arg_string(filter_str)
|
|
96
|
+
if not looks_like_filters(tokens):
|
|
97
|
+
return _full_tree()
|
|
98
|
+
try:
|
|
99
|
+
exprs = parse_filters(tokens)
|
|
100
|
+
except FilterError:
|
|
101
|
+
return _full_tree()
|
|
102
|
+
return _filtered_tree(with_ancestors(db, match_items(db, exprs)))
|
|
84
103
|
|
|
85
104
|
|
|
86
105
|
@app.post("/api/command")
|
|
@@ -89,7 +108,7 @@ def run_command(request: CommandRequest):
|
|
|
89
108
|
args, kwargs = _parse_remaining(arg_parts)
|
|
90
109
|
|
|
91
110
|
if not args:
|
|
92
|
-
return {"output": "", "tree":
|
|
111
|
+
return {"output": "", "tree": _response_tree(request.filter), "focus": None}
|
|
93
112
|
|
|
94
113
|
if args[0] == "show":
|
|
95
114
|
return _run_show(args)
|
|
@@ -101,7 +120,7 @@ def run_command(request: CommandRequest):
|
|
|
101
120
|
except TaskCLIError:
|
|
102
121
|
pass # Controller.throw_error() already printed the error into `output`
|
|
103
122
|
|
|
104
|
-
return {"output": output.getvalue(), "tree":
|
|
123
|
+
return {"output": output.getvalue(), "tree": _response_tree(request.filter), "focus": None}
|
|
105
124
|
|
|
106
125
|
|
|
107
126
|
def _run_show(args):
|