feat(tui): attention flag column + project-colored bullets + scrollbar (§8.1)

Each task row now leads with a colored attention flag (⚑ for
red/orange/blue, blank for white/none) and a project-colored bullet (●).
The bullet color is derived stably from the project id (FNV-1a → HSL →
truecolor RGB) so it survives projects being added/removed; a per-project
override on the model is a later refinement. The glyph shape is reserved
for future semantics.

The task list also gains a scrollbar and ListState-driven
scroll-to-visible so a selected task below the fold stays reachable.

Tests: fmt::project_color determinism unit; a flag-glyph render assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-06-03 10:53:19 -07:00
commit ecfe64435c
5 changed files with 116 additions and 22 deletions

View file

@ -253,16 +253,16 @@ Replaces obsidian.nvim. Telescope-backed. **Primarily the context / knowledge-ba
> **Status: daily-driver core built** (slices T1T2c; NL quick-add + search are the remaining T3 polish). The §6.2.1 Todoist study shows the dominant task activity is *interactive triage of a large set* (387 active tasks; daily orange reconfirm, blue keep/drop review, browse-by-project) — work that is awkward as either CLI flags or nvim buffers. A terminal UI owns it; the CLI (capture/scripting) and nvim (context) flank it.
- **Crate `crates/heph-tui`** ✅ — `ratatui` (which re-exports `crossterm`), a **thin client of the daemon unix socket** (reuse `hephd::Client`); never touches SQLite, same as nvim. `App` is generic over a `Backend` seam so navigation/triage logic is unit-testable without a terminal or daemon; `ui::render` is pure.
- **Layout** ✅ — three panes: **sidebar** (the five §8.2 filter views + projects) · **task list** (attention-colored rows with compact human do/late) · **preview** (canonical-context doc body + `log.tail`).
- **Layout** ✅ — three panes: **sidebar** (the five §8.2 filter views + projects) · **task list** (each row: a leading attention **flag** + a **project-colored bullet**, the title, recurrence `↻`, and a compact human do/late chip; a scrollbar appears when the list overflows) · **preview** (canonical-context doc body + `log.tail`).
- **Gestures** ✅ — `j/k` move · `Tab`/`h`/`l` focus · `a` **single-line NL quick-add** (Todoist-style: `Buy milk tomorrow p2 #Work every week` → title + attention `p1`..`p4` + do-date + `every …` recurrence + `#project`; no `#project` files it under the selected one) · `x` done · `s` skip · `A` cycle attention · `e` reschedule do-date · `b` push-to-blue · `d` drop · `D` **delete/tombstone** (y/N confirm — true soft-delete, recurring included) · `m` **move-to-project** (a list-pick overlay — "(Unfile)" then every project; backed by `task.set_project`) · `o` edit context in nvim · `/` **FTS search** (overlay; Enter opens a hit — a task at its context doc — in nvim) · `r` refresh · `q` quit. The sidebar lists the **§8.2 named filter views** — [[design]] §6.2 "filters = saved views" made interactive. Recurring tasks show a **`↻` marker**, and the **selected row expands inline** with a dimmed detail block (project · recurrence rule · do/late). *(Remaining: humanizing the displayed RRULE is later polish.)*
- **TUI ↔ nvim handoff** ✅ — `o` suspends the alternate screen and launches `nvim +"lua require('heph.node').open('<ctx-id>')"` (heph.nvim's live buffer surface), passing `$HEPH_SOCKET` so the child points at the same daemon, then restores and reloads. *(A nvim command shelling back to the TUI is later polish.)*
- **Testing** ✅ — TDD against a real daemon; headless render assertions via `ratatui`'s `TestBackend`, plus in-memory navigation/input-flow units against a fake backend.
- **Prereqs** (landed): **§8.2 filter views**; the CLI-complete task surface and `task.set_schedule`.
- **`m` move-to-project** ✅ — re-file the selected task via the `task.set_project` RPC (a list-pick overlay), closing the last Todoist-parity gap.
- **Planned UX wave** (§14 roadmap, 2026-06-03) — all client-side over the existing `RankedTask` rows:
- **flag column + project-colored bullets** — a leading **flag glyph** colored by attention (red/orange/blue; **blank for white**, freeing the bullet for project identity); the **bullet `●` colored by its project** from a stable `hash(project_id) → hue` (HSL→truecolor, 256-color fallback; overlap acceptable; the glyph *shape* is reserved for future semantics). A stored, editable per-project color override is a later refinement.
- **flag column + project-colored bullets** ✅ — a leading **flag glyph** (`⚑`) colored by attention (red/orange/blue; **blank for white/none**, freeing the bullet for project identity); the **bullet `●` colored by its project** from a stable `hash(project_id) → hue` (FNV-1a → HSL → `Color::Rgb` truecolor; overlap acceptable; the glyph *shape* is reserved for future semantics). A stored, editable per-project color override is a later refinement (derived client-side for now).
- **scrollbar** ✅ — a `ratatui` `Scrollbar` on the task list once it overflows (a `ListState` selection drives scroll-to-visible so a task below the fold stays reachable).
- **Planned UX wave** (§14 roadmap, 2026-06-03) — remaining, client-side over the existing `RankedTask` rows:
- **sort toggle `s`****default**: attention (red→orange→white→blue) → days-overdue (desc; no-date = 0) → project name → `created_at` (FIFO); **project mode**: project primary with **non-selectable `──── Name ────` separators** (`j`/`k` skip them). The view filter always runs before the sort.
- **scrollbar** — a `ratatui` `Scrollbar` on the task list when it overflows.
## 8.2 Filter views (saved agenda slices) — built
@ -447,11 +447,11 @@ See [[design]] §5§7 for the constraints later phases impose on present choi
> The remaining work is the **UX roadmap agreed 2026-06-03** (design conversation with the owner). It is documented docs-first — the bigger items have design sections above (`heph-tui` UX in §8.1, frontmatter in §8.3, wiki-links-by-id in §8.4) — and built in this order:
1. ✅ **`heph-tui` — move-to-project (§8.1) — DONE:** the **`task.set_project` RPC** (tombstone the old `in-project` link + add the new — OR-set semantics, no task-scalar op; given project must be a live project-kind node) plus the TUI `m` list-pick overlay and `heph edit --project <name>|none` (which also fixed a duplicate-link bug in the old re-file path). Unblocks the project-edit path of the frontmatter surface (§8.3).
2. **`heph-tui` task-list UX wave (§8.1) — no backend (`RankedTask` already carries every field):**
- **(a) flag column + project-colored bullets** — a leading **flag glyph** colored by attention (red/orange/blue; **blank for white**), and the **bullet `●` colored by its project** via a stable `hash(project_id) → hue` (HSL→truecolor `Color::Rgb`, 256-color fallback). Hashing is chosen for stability-under-insertion over golden-angle spread; overlap is acceptable. The bullet **glyph shape** is reserved for future semantics. A per-project **color override** (stored on the project node, editable) is a later refinement — colors are derived client-side for now (no schema change).
- **(b) sort toggle `s`** — **default**: attention (red→orange→white→blue) → days-overdue (descending; no-date = 0) → project (name) → `created_at` (FIFO). **project mode**: project is primary, with **non-selectable `──── Name ────` separator rows** between groups (`j`/`k` skip them). View filtering always runs **before** the sort.
- **(e) scrollbar** — the task list grows a `ratatui` `Scrollbar` (tracking the selection) when content overflows the pane.
- **nvim task-navigation polish (§8)** — show do/late in `next`/`list` rows and a clean jump-to-context gesture (read/navigate, not field-edit).
2. **`heph-tui` task-list UX wave (§8.1) — no backend (`RankedTask` already carries every field):**
- **(a) flag column + project-colored bullets — DONE:** a leading `⚑` colored by attention (red/orange/blue; blank for white/none), and the bullet `●` colored by its project via a stable `hash(project_id) → hue` (FNV-1a → HSL → `Color::Rgb`). Hashing is chosen for stability-under-insertion over golden-angle spread; overlap is acceptable. The bullet **glyph shape** is reserved for future semantics. A per-project **color override** (stored on the project node, editable) is a later refinement — colors are derived client-side for now (no schema change).
- **(e) scrollbar — DONE:** the task list grows a `ratatui` `Scrollbar` (tracking the selection) when content overflows; a `ListState` selection keeps the highlighted row scrolled into view.
- **(b) sort toggle `s`** — **default**: attention (red→orange→white→blue) → days-overdue (descending; no-date = 0) → project (name) → `created_at` (FIFO). **project mode**: project is primary, with **non-selectable `──── Name ────` separator rows** between groups (`j`/`k` skip them). View filtering always runs **before** the sort.
- **nvim task-navigation polish (§8)** — show do/late in `next`/`list` rows and a clean jump-to-context gesture (read/navigate, not field-edit).
3. ⏳ **Tags (§4, §8.3) — promoted from deferred:** `NodeKind::Tag` exists but has no machinery. Add **tag-as-node + an OR-set tag link** (mirroring `in-project`) + `tag.add`/`tag.remove` RPCs + enumeration. Prerequisite for the `tags:` line of the frontmatter surface (§8.3) and the eventual zk import ([[design]]); the goal is **one canonical tag set** across all of heph.
4. ⏳ **YAML frontmatter as an edit surface (§8.3) — docs-first C1:** generated-on-read, stripped-and-ignored-on-write in `heph-core`; `heph.nvim` diffs it into structured RPCs. See §8.3.
5. ⏳ **Wiki-links by node id (§8.4) — docs-first C1 (maybe C2):** canonical `[[NODEID]]` at rest, expanded/concealed for display; a `[[` picker; no name-links in the DB. Includes a one-time body fixup. See §8.4.