generated from eblume/project-template
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>
82 lines
3.4 KiB
Rust
82 lines
3.4 KiB
Rust
//! The storage abstraction (tech-spec §3.1).
|
|
//!
|
|
//! A runtime points at *something that stores nodes*; whether that is a local
|
|
//! SQLite file ([`crate::sqlite::LocalStore`]) or a remote server (a future
|
|
//! `RemoteStore`) is configuration. This trait is the seam.
|
|
|
|
use crate::error::Result;
|
|
use crate::model::{Attention, Link, LinkType, NewNode, NewTask, Node, Task, TaskState};
|
|
use crate::ranking::RankedTask;
|
|
|
|
/// A backend that can store and retrieve nodes, tasks, and links.
|
|
///
|
|
/// Methods that mutate take `&mut self`: a `LocalStore` holds an exclusive lock
|
|
/// on its file, so single-writer semantics are honest at the type level.
|
|
pub trait Store {
|
|
// --- nodes ---
|
|
|
|
/// Create a node, assigning it an id and timestamps. Returns the stored row.
|
|
fn create_node(&mut self, input: NewNode) -> Result<Node>;
|
|
|
|
/// Fetch a node by id. Returns `None` if it does not exist.
|
|
///
|
|
/// Tombstoned nodes are still returned here (callers that must exclude them
|
|
/// — `next`, `list`, `search`, `export` — filter explicitly).
|
|
fn get_node(&self, id: &str) -> Result<Option<Node>>;
|
|
|
|
/// Update a node's title and/or body. A body update re-runs markdown
|
|
/// extraction and reconciles this node's `wiki` links (tech-spec §5, §6).
|
|
fn update_node(
|
|
&mut self,
|
|
id: &str,
|
|
title: Option<String>,
|
|
body: Option<String>,
|
|
) -> Result<Node>;
|
|
|
|
// --- tasks ---
|
|
|
|
/// Create a committed task, auto-creating its canonical context `doc` and
|
|
/// the `canonical-context` link (tech-spec §6).
|
|
fn create_task(&mut self, input: NewTask) -> Result<Task>;
|
|
|
|
/// Fetch a task by its node id.
|
|
fn get_task(&self, node_id: &str) -> Result<Option<Task>>;
|
|
|
|
/// Set a task's lifecycle state. Completing a **recurring** task rolls it
|
|
/// forward in place — fresh checklist, logged occurrence, advanced do-date
|
|
/// (tech-spec §4.4) — rather than marking it done.
|
|
fn set_task_state(&mut self, node_id: &str, state: TaskState) -> Result<Task>;
|
|
|
|
/// Skip the current occurrence of a recurring task: advance its do-date
|
|
/// without logging a completion (tech-spec §4.4). Errors on a non-recurring
|
|
/// task.
|
|
fn skip_recurrence(&mut self, node_id: &str) -> Result<Task>;
|
|
|
|
/// Set a task's attention-state.
|
|
fn set_task_attention(&mut self, node_id: &str, attention: Attention) -> Result<Task>;
|
|
|
|
/// The Tactical "what is next?" ranking (tech-spec §7), using the store's
|
|
/// injected clock as `now`. `scope`, when `Some`, restricts to a project
|
|
/// node id; `red` items always appear regardless of `limit`.
|
|
fn next(&self, scope: Option<&str>, limit: usize) -> Result<Vec<RankedTask>>;
|
|
|
|
// --- links ---
|
|
|
|
/// Add a typed link between two nodes.
|
|
fn add_link(&mut self, src_id: &str, dst_id: &str, link_type: LinkType) -> Result<Link>;
|
|
|
|
/// All non-tombstoned links originating at `id`.
|
|
fn outgoing_links(&self, id: &str) -> Result<Vec<Link>>;
|
|
|
|
/// All non-tombstoned links pointing at `id` (backlinks).
|
|
fn backlinks(&self, id: &str) -> Result<Vec<Link>>;
|
|
|
|
// --- per-task log ([[design]] §6.4) ---
|
|
|
|
/// Append a line to a task's append-only log (creating the log on first
|
|
/// use). The log is the resumption breadcrumb store.
|
|
fn log_append(&mut self, task_id: &str, text: &str) -> Result<()>;
|
|
|
|
/// The task's latest `n` log entries (oldest→newest); empty if it has none.
|
|
fn log_tail(&self, task_id: &str, n: usize) -> Result<Vec<String>>;
|
|
}
|