diff --git a/docs/changelog.d/v1-prototype.feature.md b/docs/changelog.d/v1-prototype.feature.md index 6394d78..daf4b2a 100644 --- a/docs/changelog.d/v1-prototype.feature.md +++ b/docs/changelog.d/v1-prototype.feature.md @@ -27,3 +27,4 @@ Begin the v1 prototype (Phase 1, tech-spec §11.1), built in TDD slices: - Move-to-project (§8.1): a new `task.set_project` RPC re-files a task under another project (or unfiles it) with OR-set link semantics — the old `in-project` link is tombstoned and a new one added, so a task is never filed under two projects at once. In `heph-tui`, **`m`** opens a list-pick overlay ("(Unfile)" then every project) on the highlighted task. `heph edit --project ` now routes through the same RPC (fixing a bug where re-filing piled on a duplicate link), and `--project none` unfiles the task. This closes the last Todoist-parity capture gap. - `heph-tui` task-list visuals (§8.1): each row now leads with an attention **flag** (`⚑`, colored red/orange/blue; blank for white) and a **project-colored bullet** — the bullet's color is derived stably from the project id (so it survives projects being added/removed), letting you scan a mixed list by project at a glance. The list also grows a **scrollbar** and keeps the selected task scrolled into view when there are more tasks than fit. - `heph-tui` sort toggle (§8.1): **`s`** flips the task list between two orders — **default** (attention → most-overdue → project → creation) and **by-project** (grouped under dimmed `──── Project ────` separators, then the same sub-order). The view's filter still applies first. (To free `s`, **skip** moved to **`S`**.) +- `heph.nvim` task-view rows (§8): `:Heph next`/`:Heph list` rows now show a compact **do/late date chip** (and a recurrence `↻`), so you can see scheduling at a glance; `` still jumps to a task's context doc. diff --git a/docs/reference/heph-nvim.md b/docs/reference/heph-nvim.md index e28ddaf..99da295 100644 --- a/docs/reference/heph-nvim.md +++ b/docs/reference/heph-nvim.md @@ -1,6 +1,6 @@ --- title: heph.nvim -modified: 2026-06-01 +modified: 2026-06-03 tags: - reference - design @@ -84,7 +84,8 @@ doc whose owning task is followed via its `canonical-context` backlink. The `next`/`list` views render the titled rows the daemon returns (`list` enriched to carry titles + the context id, so no N+1 `node.get`) and are **interactive**: `` opens a task's context, `a` adds a task (prompt title + attention), `d` -marks the task under the cursor done, `r` refreshes. Pickers use built-in +marks the task under the cursor done, `r` refreshes. Each row also shows a +compact **do/late date chip** (and a recurrence `↻`). Pickers use built-in `vim.ui.select`, auto-upgrading to Telescope when installed. **Promotion** (`:Heph promote`) mints a committed task from the `- [ ]` line diff --git a/docs/reference/tech-spec.md b/docs/reference/tech-spec.md index d70930f..39ce07b 100644 --- a/docs/reference/tech-spec.md +++ b/docs/reference/tech-spec.md @@ -446,11 +446,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 |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):** +2. ✅ **`heph-tui` task-list UX wave + nvim nav polish (§8.1/§8) — DONE (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` — DONE:** **default**: attention (red→orange→white→blue) → days-overdue (descending; no-date = 0) → project (name) → `created_at` (FIFO). **project mode**: project is primary, with dimmed **`──── Name ────` separators** riding atop each group's first task (the cursor only lands on real tasks). View filtering always runs **before** the sort. (`skip` moved to `S` to free `s`.) - - ⏳ **nvim task-navigation polish (§8)** — show do/late in `next`/`list` rows and a clean jump-to-context gesture (read/navigate, not field-edit). *(The only piece of item 2 left.)* + - ✅ **nvim task-navigation polish (§8) — DONE:** `:Heph next`/`list` rows now carry a compact **do/late date chip** (and a recurrence `↻`); `` already jumps to a row's canonical-context doc (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. diff --git a/heph.nvim/lua/heph/view.lua b/heph.nvim/lua/heph/view.lua index fbc10fa..4ceabe2 100644 --- a/heph.nvim/lua/heph/view.lua +++ b/heph.nvim/lua/heph/view.lua @@ -17,9 +17,46 @@ local HINT = " open a add d done r refresh" local ATTENTIONS = { "white", "orange", "red", "blue" } +-- Compact relative date for a do/late epoch-ms value (mirrors heph-tui's fmt): +-- today / tomorrow / yesterday, MM-DD within the year, else YYYY-MM-DD. +local function fmt_date(ms) + local d = os.date("*t", math.floor(ms / 1000)) + local n = os.date("*t") + local d_noon = os.time({ year = d.year, month = d.month, day = d.day, hour = 12 }) + local n_noon = os.time({ year = n.year, month = n.month, day = n.day, hour = 12 }) + local days = math.floor((d_noon - n_noon) / 86400 + 0.5) + if days == 0 then + return "today" + elseif days == 1 then + return "tomorrow" + elseif days == -1 then + return "yesterday" + elseif d.year == n.year then + return string.format("%02d-%02d", d.month, d.day) + else + return string.format("%04d-%02d-%02d", d.year, d.month, d.day) + end +end + +-- The right-side date chip: a late marker once past due, else the do-date. +local function date_chip(t) + if t.late_on and os.time() * 1000 > t.late_on then + return "late:" .. fmt_date(t.late_on) + elseif t.do_date then + return "do:" .. fmt_date(t.do_date) + end + return "" +end + local function row(t) local tag = t.attention and ("[" .. t.attention .. "]") or "[ ]" - return string.format("%s %s", tag, t.title) + local recur = t.recurrence and " ↻" or "" + local left = string.format("%s %s%s", tag, t.title, recur) + local chip = date_chip(t) + if chip ~= "" then + return string.format("%-50s %s", left, chip) + end + return left end local function task_on_line(buf) diff --git a/heph.nvim/tests/e2e/view_spec.lua b/heph.nvim/tests/e2e/view_spec.lua index fc88358..7bc12e1 100644 --- a/heph.nvim/tests/e2e/view_spec.lua +++ b/heph.nvim/tests/e2e/view_spec.lua @@ -33,6 +33,18 @@ describe("filter views", function() assert.is_falsy(text:find("cool thing", 1, true), "blue task should not be in ToM") end) + it("shows a do-date chip on a dated task row", function() + -- A past do-date keeps the task actionable (so it appears in ToM) and, with + -- no late_on, renders as a `do:` chip rather than a `late:` one. + ctx.q:call("task.create", { title = "dated thing", attention = "red", do_date = 1704067200000 }) + + require("heph.view").view("tom") + local buf = vim.api.nvim_get_current_buf() + local text = table.concat(vim.api.nvim_buf_get_lines(buf, 0, -1, false), "\n") + assert.is_truthy(text:find("dated thing", 1, true), "dated task missing") + assert.is_truthy(text:find("do:", 1, true), "do-date chip missing from the row") + end) + it("scopes the chores view to chore projects via the daemon", function() local chores = ctx.q:call("node.create", { kind = "project", title = "Chores" }) ctx.q:call("task.create", { title = "take out trash", attention = "white", project_id = chores.id })