A spoke could be silently failing to sync (expired token → 401, or hub
unreachable) with the only signal buried in the daemon log. Now:
- hephd tracks SyncHealth (last attempt/success time, last error, auth-failure
flag) from the background sync loop and sync.now, classifying a 401 as an auth
failure. sync.status returns it plus the pending merge-conflict count.
- heph-tui shows a live status-line indicator (spoke only): '⟳ <age>' since the
last good sync, red '⚠ auth' when re-login is needed, '⚠ offline' when the hub
is unreachable, and '⚠ N conflicts' when conflicts are pending. The event loop
polls on a 2s tick so the age advances and failures appear while idle.
- docs: recommended Authentik access/refresh token validity to stop frequent
re-logins (with the iOS PWA localStorage-eviction caveat).
Closes the 'Add hub connection status to heph-tui' and 'Spoke sync health:
surface unhealthy state instead of silent 401 spam' backlog items.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bundles the cosmetic/UI-polish backlog for the agenda surfaces. All read-side;
no schema or sync change (see hub-spoke-data-evolution).
- humanize_rrule (hephd::datespec): inverse of parse_recurrence — renders an
RRULE as 'every other week', 'weekdays', 'yearly on Apr 15', etc.; falls back
to the raw rule for unmodeled parts (COUNT/UNTIL/ordinal BYDAY). Mirrored in
the PWA's datespec.js. Shown in the TUI recurs detail line and PWA task/qa
previews instead of the raw FREQ= string.
- project.overview RPC + Store::project_overview: each project's parent (via the
existing 'parent' links) and direct outstanding-task count, a read-only query.
- TUI sidebar: subprojects indented by depth, per-project counts, wider pane,
and ListState + scrollbar so it scrolls instead of clipping on overflow.
Tests: humanize parity (Rust + JS), round-trip through parse_recurrence,
raw-passthrough; project_overview count/parent; sidebar tree ordering + cycle
safety.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- `<Enter>` now opens the selected task's context doc in nvim (App::enter:
from the sidebar it drills into the task list first); the `o` binding is
retired. Hint line updated.
- BUILTIN_VIEWS reordered to the owner's preference — Top of Mind, Tasks,
Work Tasks, Chores, On Deck — which drives the TUI sidebar and
`heph view`. Tests that walked to On Deck by a fixed offset now seek it
by title.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
`D` arms a delete on the highlighted task; the status line shows
"Delete \"title\"? (y / N)" and the next key confirms (y) or cancels (anything
else). Confirming calls node.tombstone — a true soft-delete that removes the
task from every view, recurring tasks included (unlike `x` done, which rolls a
recurring task forward, or `d` dropped, which keeps it in the store). Backend
gains `tombstone`.
Tests: confirm-flow unit test against a recording fake (arm → cancel keeps it;
arm → confirm tombstones), plus a real-daemon integration test that deleting a
recurring task drops it from the view and sets the node's tombstoned flag.
186 workspace tests; clippy/fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recurring tasks now show a ↻ marker on their row, and the highlighted task
expands inline beneath itself with a dimmed detail block: project name,
recurrence rule, and do/late dates (only the fields that are set). Project
name resolves client-side from the sidebar; dates were already on the row.
Backend: RankedTask gains `recurrence: Option<String>` (populated in
ranked_from_row from t.recurrence; both list/next select lists updated) — the
only data the row was missing. Serializes over the socket automatically.
Tested: a real-daemon render test asserts the ↻ glyph plus the selected
detail block (recurs: FREQ=DAILY, project: Routines). 184 workspace tests;
clippy/fmt clean.
Note: the recurrence is shown as the raw RRULE for now (humanizing it is a
later polish). Subtask/checklist folding was dropped — those reference items
turned out to be blue backlog items, not sub-items.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`/` opens a search prompt; submitting runs the FTS `search` RPC and overlays
the results on the center pane (title + [kind]). j/k move, Enter opens the hit
(a task hit opens its canonical-context doc via context_of; docs/journals open
themselves) in nvim, Esc exits search. Backend gained `search` + `context_of`.
Tests: fake-backend flow (results populate; task hit resolves to its context,
doc hit to itself; clear) + a real-daemon integration test (seed a doc, search,
assert the hit + that the Search pane renders). 183 workspace tests; clippy/fmt
clean.
Move-to-project is the last Todoist-parity gap; it needs a new task.set_project
RPC (no link-remove RPC yet) and is deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`a` is now Todoist-style one-line capture: parse a line like
`Water plants tomorrow p2 #Camano Chores every 3 days` into title + attention
(p1 red / p2 orange / p3 blue / p4 white) + do-date (today/tomorrow/+3d/fri/ISO)
+ recurrence (`every …`, longest suffix that parses) + project (`#Name`, greedy
multi-word match against existing projects). An unresolved `#tag` stays in the
title verbatim (no surprise project creation); with no `#project`, the task is
filed under the selected sidebar project.
The parser (`quickadd::parse`) is pure — `today` and the project list are
passed in — reusing hephd::datespec for dates/recurrence, so it's exhaustively
unit-tested (priority, relative/weekday dates, single + multi-word projects,
recurrence extraction, unresolved tags, the all-at-once case, and the
"every"-not-a-recurrence fallback). `Backend::create_task` gained a recurrence
arg. The multi-step guided add it replaces is gone.
181 workspace tests; clippy/fmt clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single-keypress mutations on the highlighted task, each → RPC → reload with a
status confirmation: x done (recurring roll-forward), d drop, s skip, A cycle
attention (white→orange→red→blue, §6.2), b push-to-blue (On Deck). The bulk of
daily triage — the daily orange reconfirm and blue keep/drop review made fast.
Tests: next_attention cycle unit test; integration tests against a real daemon
that completing/pushing-to-blue removes a task from Top of Mind (and it then
shows under On Deck). 11 heph-tui tests; clippy/fmt clean.
Input-requiring actions (a add, e reschedule) + nvim context handoff are T2b.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New crate crates/heph-tui: a ratatui terminal agenda, thin client of the
hephd unix socket (never touches SQLite, same as heph.nvim). The next big
surface — the interactive triage UI the §6.2.1 Todoist study calls for.
- 3-pane layout: sidebar (the five §8.2 filter views + projects), task list
(attention-colored rows with compact human do/late dates), and a preview
pane (the highlighted task's canonical-context doc body + log tail).
- App state is generic over a `Backend` seam, so navigation/selection logic
is unit-testable without a terminal or daemon; `ClientBackend` forwards to
the socket. Rendering is a pure `ui::render(frame, &app)`.
- Navigation: j/k within the focused pane, Tab / h / l to move focus,
selecting a sidebar source reloads the list, moving the task cursor
refreshes the preview. r refresh, q quit.
- Socket resolution: --socket flag, then $HEPH_SOCKET, then the standard
runtime path (the TUI honors the env var the CLI doesn't).
Tests: a headless TestBackend render against a real spawned daemon (asserts
views/projects/tasks/preview paint, and Top of Mind excludes blue), plus
in-memory navigation unit tests. 8 heph-tui tests; clippy/fmt clean.
Mutations (add/done/attention/reschedule/blue) + nvim handoff land in T2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>