Commit graph

17 commits

Author SHA1 Message Date
9a487cbe3b feat(heph-tui,heph-pwa): humanized recurrence + indented/counted/scrolling project sidebar
All checks were successful
Build / validate (pull_request) Successful in 6m57s
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>
2026-06-05 17:44:43 -07:00
dce3519345 feat: heph list --project <name> + --json; thin AGENTS.md
Some checks failed
Build / validate (pull_request) Failing after 3m21s
`heph list --project <name>` lists a project's outstanding tasks by name
(subtree-expanded, resolved server-side via a new project.scope path that
reuses the view machinery; errors on unknown names). `--json` prints raw
rows — node_id, canonical_context_id, attention/state/do_date/late_on/
recurrence/project_id — for scripting and agents. Store::project_scope on
the trait + LocalStore + RemoteStore; new project.scope RPC and a flattened
ListParams so `list` accepts an optional project name. Test covers
resolve-by-name + unknown-name error.

AGENTS.md thinned to tight command/pattern sections: dropped the historical
parity narrative and the verbose roadmap section; added a "Working state"
section documenting `heph list --project Hephaestus [--json]` as the way to
inspect heph's self-hosted roadmap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 20:38:57 -07:00
dd5ef7dc63 fix: deleting a project unfiles its tasks to the Inbox (§8.1/§8.2)
Some checks failed
Build / validate (pull_request) Failing after 11s
Project delete previously tombstoned only the project node, leaving its
tasks with a live in-project link to a dead project — orphaned (not in the
Inbox, unbrowsable, blank project) rather than unfiled as intended. New
atomic Store::delete_project tombstones every in-project link to the project
(tasks fall to the Inbox), then tombstones the project node; tasks are never
deleted. Exposed as the project.delete RPC (LocalStore + RemoteStore); the
heph-tui sidebar `D` now routes through it. Core test asserts the task
survives and becomes unfiled; the project node is tombstoned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 19:15:54 -07:00
01ae561a74 feat: Inbox view — outstanding tasks with no project (§8.2)
A sixth built-in filter view (listed below On Deck) showing every
outstanding task filed under no project — the capture inbox to triage. New
ListFilter.unfiled predicate kept purely in matches(); the inbox ViewSpec is
un-gated (no attention/do-date filter) so nothing hides from triage. Surfaces
automatically in the heph-tui sidebar and `heph view`. Tests cover the
predicate and the view spec; navigation tests updated for the 6th view.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 18:12:33 -07:00
df7f43788b feat(core): task.set_project — move-to-project with OR-set link semantics (§8.1)
Add `Store::set_task_project` (heph-core + RemoteStore) and the
`task.set_project` RPC: tombstone the task's existing `in-project` link(s)
and add a new one (or none, to unfile). A given project id must name a
live project-kind node, else InvalidArg/NodeNotFound.

Route `heph edit --project` through it, fixing a duplicate-link bug (the
old path added an in-project link without removing the prior one);
`--project none` now unfiles. Factor a `links::tombstone` helper out of
`sync_wiki_links`.

Tests: core move/unfile/reject + a duplicate-link regression; a socket
dispatch test. The TUI `m` gesture follows in the next commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 10:35:16 -07:00
1833863594 feat(tui): recurring-task glyph + inline detail under the selected row (§8.1)
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>
2026-06-03 08:08:16 -07:00
a5fc578525 feat(views): filter views (§8.2) — saved agenda slices
Some checks failed
Build / validate (pull_request) Failing after 18m44s
Make the owner's saved filters first-class so the agenda isn't one flat
list. `list` now takes a ListFilter predicate-as-data (heph-core::filter):
attention include/exclude sets, project-id scope, exclude_projects, and an
actionable do-date gate. New Store::view(name) resolves a built-in ViewSpec
— looking project names up to ids and subtree-expanding them through parent
links — then lists.

Five built-ins seeded from the Todoist queries (design §6.2.1): tom, ondeck,
chores, work, tasks (Schedule dropped — time-of-day isn't modeled on
date-grained do-dates). Surfaced as `heph view <name>` (no name lists them),
the `view` RPC + RemoteStore forward, and `:Heph view <name>` in nvim.

The list RPC/RemoteStore/CLI/heph.nvim migrate to the filter wire; legacy
--scope/--attention/--no-blue map onto it (nvim view.lua updated).

Tests: filter unit predicate, a views integration suite (subtree
scope+exclude, actionable gate, unknown-view error, absent-project empties),
a socket list/view dispatch test, two nvim e2e specs. 154 Rust tests + 18
nvim e2e green; clippy/fmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 06:39:07 -07:00
70d5af5bdc feat(core): task.set_schedule — reschedule do-date/late-on/recurrence
Some checks failed
Build / validate (pull_request) Has been cancelled
There was no way to change a task's do-date, late-on, or recurrence after
creation (only attention/state had setters) — a real reschedule gap. Add a
single patch method covering the three schedule scalars with no setter.

- model: SchedulePatch with double-option fields (absent=leave, null=clear,
  value=set), serde-skips absent fields so the distinction round-trips
- Store::set_task_schedule + LocalStore/RemoteStore impls; sqlite set_schedule
  overlays present fields then records the LWW task.set op (sync-correct)
- rpc dispatch: task.set_schedule (id + flattened patch)
- tests: core set/clear/leave + missing-task; rpc_socket round-trip asserting
  the absent/null/value semantics over the wire

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 19:26:25 -07:00
b97c387252 heph.nvim: context-item promotion + Dagger headless-nvim CI (slice 11c)
Some checks failed
Build / validate (pull_request) Failing after 3s
Backend (TDD):
- task.promote {container_id, item_ref, attention?, project?}: mint a committed
  task from the item_ref-th `- [ ]` context item (1-based, document order via a
  new extract::context_item_lines) and rewrite that source line into a [[link]]
  to it. Unit + rpc_socket tests.
- resolve_id now excludes canonical-context docs, so [[Task Title]] resolves to
  the task, not its identically-titled context doc (deterministic; a general fix
  surfaced by promotion's ULID-tiebreak ambiguity).

Plugin: :Heph promote / promote_under_cursor (save-if-dirty → compute item index
with a code-fence-aware scanner mirroring extract.rs → task.promote → reload the
rewritten buffer). e2e spec (f): promote a context line, assert the new task in
next, the source line became a link, and the container backlinks the task.

CI via Dagger: a test_nvim function bakes a pinned, arch-detected Neovim
(v0.11.2 — Debian's is too old for vim.uv) onto rust:1-bookworm, builds hephd,
and runs the self-contained shim suite (cargo + target cache volumes);
build.yaml calls `dagger call test-nvim`. run.lua now fails on zero specs (no
false-green). Validated end-to-end: passing suite → exit 0, failing spec →
Dagger exit 1.

117 Rust tests + 7 nvim e2e specs green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 06:08:41 -07:00
7c9a734ebd heph.nvim: task views — next/list/capture/attention/state/log (slice 11b)
Some checks failed
Build / validate (pull_request) Failing after 2s
Backend: enrich `list` to return titled RankedTask rows (title +
canonical_context_id, via a shared ranked_from_row with `next`), so the
Organizational view needs no N+1 node.get. TDD: query_surface test asserts
list rows carry title + context id.

Plugin:
- view.lua: Tactical `next` + Organizational `list` rendered scratch buffers;
  <CR> opens the row's canonical-context doc. Narrowed the node autocmd to
  heph://node/* so view buffers (heph://next, heph://list) don't trip it.
- task.lua: capture, set-attention, done/drop, skip, per-task log append, all
  resolving "the current task" from the buffer (a task node, or a context doc
  via its canonical-context backlink).
- picker.lua: vim.ui.select with Telescope auto-upgrade (headless-safe).
- command.lua: :Heph next/list/capture/attention/done/drop/skip/log/search.

e2e: capture→next→open context→add/check checklist→done; recurring
fresh-checklist (complete rolls forward in place, next occurrence all-unchecked
— the §4.4 hard requirement). 6 specs green via `mise run test-nvim`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 21:12:56 -07:00
455f172a54 heph-core: body text-CRDT via yrs (sync 8d)
Some checks failed
Build / validate (pull_request) Failing after 4s
Replace last-writer-wins for node bodies with the yrs text CRDT, so
concurrent edits to different regions of a body merge instead of one
clobbering the other (tech-spec §5, §12).

- New crate::crdt module wraps yrs: a device authors under a stable
  client_id derived from its sync origin; a whole-buffer write is diffed
  (common prefix/suffix, char-boundary safe) into the doc and the yrs
  delta is captured; merge is commutative/idempotent.
- nodes::create/update/journal maintain the body_crdt BLOB and put the
  yrs delta in the node.create/node.set op payload (body_crdt field).
  Recurrence's local checklist reset goes through the same path to keep
  body and body_crdt consistent (still records no op, as before).
- apply::node_upsert merges the body delta through the CRDT regardless of
  HLC order and drops body-conflict recording; titles + task scalars stay
  LWW with the conflict queue.
- convergence test now asserts disjoint concurrent body edits both survive
  and enqueue no conflict.

97 tests green; clippy -D warnings + fmt + prek clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 09:06:17 -07:00
b05ddf4bb5 heph-core: op-log recording + merge/apply engine (sync 8b/8c)
Some checks failed
Build / validate (pull_request) Failing after 3s
The conceptual core of sync: every mutation records an Op, and foreign
ops are applied with merge rules to converge replicas (tech-spec §12).

Recording (8b): each node/task/link mutation appends an oplog Op stamped
with its HLC — node.create/set/tombstone, task.create/set, link.add/
remove. `Store::ops_since(cursor)` is the push cursor.

Merge/apply (8c): `Store::apply_op` replays a foreign op idempotently —
  - bodies/titles + task scalars: last-writer-wins by HLC; a discarded
    cross-device value is recorded in `conflicts` (surfaced, not dropped);
  - links: OR-set add/remove by link id;
  - tombstones: monotonic.
The local clock absorbs each applied HLC. `conflicts_list`/
`conflicts_resolve` expose the queue. `adopt_owner` rewrites a replica to
a canonical user id (basic §13 adoption) so replicas can share data.

13 tests: HLC stamping (4) + 6-case two-replica convergence (round-trip,
idempotency, scalar LWW + conflict, body LWW, link OR-set, monotonic
tombstone). 102 tests green. Body merge is LWW for now; the yrs text
CRDT (8d) upgrades concurrent body edits to auto-merge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 21:29:20 -07:00
c81d45a291 heph-core: real HLC + persistent device origin (sync 8a)
Some checks failed
Build / validate (pull_request) Failing after 4s
First sync slice. Ratifies yrs for body merge in the tech-spec.

- hlc module: Hlc (physical, counter, origin) with a fixed-width encoding
  whose lexical order equals causal order; HlcClock generator (tick/update)
  — clock-injected, strictly monotonic. Unit + 2 proptests.
- meta table (migration v3) holds the stable per-device `origin` and the
  last HLC. next_hlc() does a read-modify-write inside the caller's
  transaction (store is single-writer, so no race), replacing the
  timestamp placeholder. Every node write is now stamped with a real,
  monotonic, causally-ordered HLC.

4 stamping integration tests (monotonic under stalled/regressed clock;
origin shared + persists across reopen; HLC resumes from persisted state).
89 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 21:13:55 -07:00
d749c2a428 heph-core: Organizational list, health, journal (§6, §7)
Round out the local query surface ahead of the distributed layer:

- list(scope, attention, include_blue): the Organizational survey —
  outstanding committed tasks incl. backlog, with project/attention
  filters and an include_blue toggle.
- health(): working-set tensions surfaced honestly — orange / active
  (white+orange+red) / on-deck (blue) counts; conflict_count + sync_status
  reserved for sync.
- journal.open_or_create(date): deterministic id in (owner, ISO-date)
  (§3.1) so offline replicas converge; idempotent; rejects non-ISO dates.
- Exposed over RPC (list / health / journal.open_or_create).

6 integration tests; 76 total green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 20:40:33 -07:00
d0debfceb9 heph-core: recurrence (roll-forward in place) + per-task logs
Some checks failed
Build / validate (pull_request) Failing after 4s
Slice 5 (tech-spec §4.4). Completing a recurring task rolls it forward in
place instead of marking it done — the Todoist-corner-avoiding model.

Pure recurrence module:
- next_occurrence(rrule, anchor, after): lazy RRULE expansion (rrule +
  chrono/UTC) returning the next instance strictly after `after`,
  skipping missed occurrences; None when a finite series is exhausted.
- reset_checkboxes(body): the fresh-checklist transform — unchecks every
  `- [x]`, idempotent, preserves indentation/bullet/line-endings.

Storage roll-forward (one transaction, on set_state(done) of a recurring
task): reset the canonical context doc's checklist, append the completed
occurrence to the task's log, advance do_date to the next instance after
now (skipping misses); finite series finally goes done. `skip` advances
the same way without logging. Non-recurring done is unchanged.

Per-task append-only log (`log-of` doc): log_append / log_tail — the
resumption breadcrumb + recurring-completion narrative ([[design]] §6.4).

Tests: 7 recurrence unit + 2 proptests (no checked marker survives reset;
reset idempotent for any body) + 6 end-to-end incl. five-occurrence
no-carry-forward and missed-collapse-to-one. 53 tests green. This
completes the heph-core library layer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:14:22 -07:00
7f63f926d0 heph-core: "what is next?" ranking (tech-spec §7)
Some checks failed
Build / validate (pull_request) Failing after 3s
Slice 4 — the flagship Tactical blank-slate engine. Pure and
clock-injected, two stages:

- Candidacy filter: committed ∧ outstanding ∧ ¬tombstoned ∧ ≠blue ∧
  actionable (do_date NULL or ≤ now) ∧ in scope. do_date is used ONLY
  here — a boolean "can I do this now?" gate, never urgency.
- Order: an ordered list of named Dimensions applied lexicographically
  (PastLateOn → LateOverdueAmount → Attention band → CreatedAt FIFO),
  with node_id as final tiebreak for a total order. Reorder RANKING in
  one place to retune. late_on is the sole urgency signal (global tier);
  age never becomes urgency. blue hidden; red always shown past limit.

Storage `Store::next` loads candidates via a SQL join (project +
canonical-context links) and runs the pure engine with the store clock.

13 table-driven unit cases + 3 proptests (antisymmetry, sorted output
fully ordered, equality ⇒ identity) + 2 end-to-end. 38 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:07:16 -07:00
b9d2072f75 heph-core: tasks, links, canonical-context doc
Some checks failed
Build / validate (pull_request) Failing after 3s
Slice 3 (tech-spec §4.2–§4.3, §6). Refactor the SQLite layer into focused
submodules (nodes/tasks/links) behind a thin delegating Store impl so a
transaction can span several.

- Model: Attention (white/orange/red/blue), TaskState
  (outstanding/done/dropped), LinkType, Link, Task, NewTask.
- create_task: in one transaction mints the task node + tasks row, the
  canonical context doc, the canonical-context link, and an optional
  in-project link. get_task / set_task_state / set_task_attention.
- Links CRUD: add_link, outgoing_links, backlinks (non-tombstoned).
- update_node: a body change re-runs extraction and reconciles this
  node's wiki links — diff-based and idempotent, resolved via alias then
  exact title (owner-scoped); unresolved targets link on a later edit
  once the target exists; dropped targets are tombstoned.

20 tests green (12 unit + 8 integration).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 19:02:35 -07:00