Documentation
¶
Overview ¶
Package mutate is the single ULID-keyed mutation engine shared by the CLI and (later) the TUI, implementing the write contract from SPEC §6.1–6.3. Every write to tasks.txt goes through Apply: acquire the lock, re-read the file from disk, run the caller's mutation against the fresh Doc while recording before-images, append the undo transaction, then atomically write. Nothing else writes tasks.txt, so a concurrent `nt add` can never be clobbered by a stale in-memory snapshot.
Index ¶
- func Complete(d *task.Doc, rec *Recorder, t *task.Task, today string)
- func Today() string
- type DoctorReport
- type Engine
- func (e *Engine) Apply(op string, fn func(d *task.Doc, rec *Recorder) error) error
- func (e *Engine) Archive() (int, error)
- func (e *Engine) Doctor(apply bool) (DoctorReport, error)
- func (e *Engine) PeekUndo() (label string, isRedo bool, ok bool)
- func (e *Engine) Read() (*task.Doc, error)
- func (e *Engine) RenameNote(src *note.Note, all []*note.Note, dest string) (newRel string, updated int, err error)
- func (e *Engine) Undo() (op string, did bool, err error)
- type Recorder
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Complete ¶
Complete marks a task done, spawning the next occurrence if it recurs (SPEC §9). Both the completion and the spawned task are recorded in the same undo transaction (rec captures the original's before-image and the spawn as an add), so a single `nt undo` reopens the original and removes the new one (SPEC §6.3). Must run inside an Engine.Apply closure.
Types ¶
type DoctorReport ¶
type DoctorReport struct {
DupIDsRemoved int // duplicate-ULID lines dropped (within tasks.txt)
CrossFileDups int // tasks dropped from tasks.txt because done.txt has them
IDsAssigned int // task lines that lacked an id and got one
Actions []string // human-readable, one per fix
Warnings []string // structural problems doctor reports but can't auto-fix
}
DoctorReport summarizes what Doctor found (and, unless dry-run, fixed).
func (DoctorReport) HasProblems ¶ added in v0.4.0
func (r DoctorReport) HasProblems() bool
HasProblems reports whether anything is wrong — fixable issues OR warnings — so `nt doctor --check` exits non-zero on a dependency cycle / dangling edge even when there's nothing to rewrite.
func (DoctorReport) Issues ¶
func (r DoctorReport) Issues() int
Issues is the count of auto-fixable problems (used to decide whether to write).
type Engine ¶
Engine owns the store and serializes all task-file writes.
func (*Engine) Apply ¶
Apply runs fn under the lock against a freshly-read Doc, journals the resulting transaction, and atomically writes the file. fn mutates the Doc and uses rec to record what it touched.
func (*Engine) Archive ¶
Archive moves completed tasks from tasks.txt to done.txt under the lock. It is an explicit housekeeping action and is intentionally not journaled — the cross-file move is the open question in SPEC §15, so `nt undo` does not revert it (the CLI says so). Returns the number of tasks moved.
func (*Engine) Doctor ¶
func (e *Engine) Doctor(apply bool) (DoctorReport, error)
Doctor reconciles tasks.txt after a git merge (or hand-editing): it drops duplicate-ULID lines that a `merge=union` merge can leave behind, and assigns ids to any task line missing one. Like Archive it runs under the lock and is intentionally NOT journaled — it's a repair, and git is the recovery path for a git-tracked store. With apply=false it reports without writing (dry-run).
Duplicate resolution keeps one line per id, preferring a completed line so a "done on one branch" state is never lost.
func (*Engine) PeekUndo ¶ added in v0.4.0
PeekUndo reports what the next reversal targets, without changing anything: the human label (with the internal "redo:" prefixes stripped) and whether the pending journal entry is a redo (an odd number of "redo:" prefixes) rather than a fresh forward op. ok is false when the journal is empty. It is a display-only read of the atomically-written journal, so it runs lock-free.
func (*Engine) Read ¶
Read returns the current parsed document (no lock; read-only callers such as list/search tolerate a consistent-at-read-time snapshot).
func (*Engine) RenameNote ¶
func (e *Engine) RenameNote(src *note.Note, all []*note.Note, dest string) (newRel string, updated int, err error)
RenameNote renames or moves a note file and rewrites every [[link]] to it across tasks.txt and notes/ (preserving each link's folder prefix, #fragment, and |alias). dest is a new name or a folder/path relative to notes/ (.md optional); a bare name keeps the note in its current folder. It refuses a destination whose basename collides with another note (which would make bare links ambiguous). A pure move (same basename) needs no link rewrite, since resolution is by path-suffix. Like Archive it is not a single undo transaction.
func (*Engine) Undo ¶
Undo reverts the most recent transaction (SPEC §6.3). Under the lock it:
- validates that current state still matches the transaction's recorded post-image by ULID — if the world moved underneath (another writer changed/removed a touched task), it refuses rather than corrupting state;
- applies each change's inverse (restoring before-images by ULID) WITHOUT resurrecting a task another writer removed;
- durably writes the reverted tasks file FIRST, then removes the journal entry and records a swapped redo transaction — so a tasks-write failure can't lose the inverse (it stays in the journal, retryable).
The returned op label names what was undone; did is false when there is nothing to undo.
type Recorder ¶
type Recorder struct {
// contains filtered or unexported fields
}
Recorder accumulates the before/after images of touched tasks so a single undo transaction can reverse the whole mutation.