Skip to content

v1.18.0

Choose a tag to compare

@github-actions github-actions released this 30 Sep 21:03
· 3 commits to main since this release

Features

  • The Task resource carries its lifecycle dates, quadrant, and agent readiness. (#34) GET /api/v1/tasks/{id} and every task row now include created (the day from the Captured [YYYY-MM-DD]. note a capture writes — the store keeps no separate creation stamp, so a body edit that removes the note reports null), updated (the UTC instant of the record's stored last-write stamp, null when it has none), archived_on (the day an archived task's subtree was swept, inherited from the stamped subtree root), quadrant (Q1–Q4 from the same classifier and configured urgent_days as tasks quadrants, null for proposed, closed, and archived tasks), and agent_ready (the exact rule scope=agent_ready and tasks list --agent-ready apply, now one shared function). archived: true is unchanged. A client can sort by recently touched and render a detail pane without re-deriving any of these rules.
  • Activate a task over HTTP. (#28) POST /api/v1/tasks/{id}/activate is tasks activate for API clients: no body, mandatory If-Match, the task back with its new ETag, one undo step. It runs the same application operation as the CLI (which now goes through it too), so it keeps a past available-from date, clears a future one, and on a lead or recurring task releases just this occurrence — none of which PATCH {deferred: false, scheduled: null} could express. A web client's defer control now has all three of the TUI's choices. /meta advertises capabilities.activate.
  • Tag and note deltas on PATCH. (#29) add_tags, remove_tags, add_contexts, remove_contexts, and append_body send the store's own deltas — the ones tasks tag and tasks note use — instead of making a client read, edit, and resend the whole list or body. They are refused (422) alongside the fields they edit. A PATCH made only of deltas may send If-Match: *, so a note appended from a web form lands beside an agent's concurrent priority edit instead of failing 412; a real revision keeps its whole-task meaning, and * on anything else — a PATCH that replaces a field, or any other task write (activate, delete, the delegation actions) — is 428. A blank or whitespace-only append_body is refused, as tasks note refuses it. Appending is now a store operation (body_append) composed under the lock, which tasks note also uses. A PATCH that only nulls dates now runs the store's single date-clear operation, the one tasks undate uses. Each delta PATCH's undo step is labelled like the CLI verb it duplicates (note: …, tags: …, remove deadline: …), in GET /history as in tasks history. /meta advertises capabilities.patch_deltas.
  • Writes say what else they changed. (#33) Task write responses carry an optional meta.effects: rolled: {from, to} when completing a recurring task rolled it forward instead of closing it, and touched_ids when the write changed tasks besides the one it returns — a completion that cascaded DONE over open descendants, or a move that carried descendants with it. Both come from the store's own report of the transaction, not from comparing reads. DELETE ?cascade=true now answers 200 with {deleted: [...]} — every removed task, root first, as it stood before the delete, the shape tasks delete --json reports; a plain delete still answers 204.
  • The API routes the manager endpoints. (#25) A web client can now do what the TUI's u, ctrl-r and x do without shelling out. GET /api/v1/history peeks the next undo and redo labels with the store_revision they apply to, and POST /api/v1/history/undo and /redo require that revision back, refusing with 409 conflict (details.reason: stale_store_revision, with the current revision) when any surface wrote in between, so a stale browser tab can never undo someone else's edit; an exhausted journal and an out-of-band edit answer empty and journal_conflict. GET /api/v1/archive-preview lists the roots, descendants and every id a sweep would move, each blocked root with its open children, and a fingerprint; POST /api/v1/archive-sweeps moves them only while that fingerprint still holds, and every refusal carries the CLI's own reason word. The CLI gains the same two steps: tasks history [--json], undo/redo --store-revision REV, tasks archive --dry-run and archive --fingerprint FP, with documents byte-identical to the HTTP data. The CLI and OpenAPI vocabularies are reconciled on the CLI's names ({roots, records, moved_ids}, a blocked row list), undo --json adds store_revision, and a journal conflict now carries reason: journal_conflict. /meta advertises undo, redo and archive_sweep as true.
  • Change detection over HTTP is cheap, and can be pushed. (#26) GET /api/v1/meta answers If-None-Match with 304 and no body, deciding from a digest of the two files without parsing, validating or snapshotting the store, and is served Cache-Control: no-cache so a browser fetch() revalidates on its own. Its ETag is opaque and covers the document as well as the store — the revision joined to a digest of the process's configuration, build and capabilities — so restarting the API under a different zone, date order, urgent_days or link config never answers a stale 304; meta.store_revision stays the plain revision. GET /api/v1/events is live: a Server-Sent Events stream of store.changed frames carrying the new store_revision (also the event id, so a reconnecting EventSource is not told what it already knows), with a first frame on connect, a keep-alive comment when idle, at most eight open streams (503, reason: stream_limit, Retry-After beyond that), and a clean end on client disconnect or server shutdown. capabilities.events is true.
  • The TUI's tabs are readable over HTTP. (#23) GET /api/v1/views/{agenda,next,quadrants,inbox} is now routed. Each returns the full Task resource for every row (revision included) in the view's canonical order, plus a defined groups layout that references those rows by id: the agenda's overdue / today / tomorrow / later day buckets in the server's zone, one next group per @context (a two-context task in both) with (no context), all four quadrants Q1..Q4 with their headings, and the intake tab's two blocks — Approvals in triage order, then the accepted Inbox — each grouped by project in the Projects view's order with unfiled last. include_unavailable=true mirrors the TUI's Z toggle. The selection and grouping rules moved out of the TUI into internal/taskquery, so the tabs and the route are computed by the same functions and a web client no longer ports them. /meta advertises capabilities.views, an unknown view name is a 404 that lists the routed ones, and an unrouted method or path now says "No endpoint matches that method and path." instead of "No task with that id."
  • An outline read, and section actions on every section. (#24) GET /api/v1/views/outline returns the Outline tab as one flat DFS list of nodes — sections and tasks interleaved in file order, each with its shown parent and depth — so a client can rebuild the tree exactly, including where a project's tasks sit relative to its sub-sections. Section nodes carry the section's role, note, lifecycle and task tallies (shown, open, closed, and the · N closed count the toggle is hiding); task nodes carry full Task resources; include_closed=true mirrors the TUI's C toggle, and proposals stay out as they do in the tab. GET /api/v1/sections now carries kind, body, state and closed too. The project action routes — rename, complete, drop, reopen, archive — accept any section the Outline shows, not only what /projects lists, so a nested sub-section or an area with no open work is no longer a 404 over HTTP while the TUI can act on it; the Inbox and the Projects heading refuse rename, close and archive with a 409 instead, and reopen always works. That refusal lives in the application layer, so the TUI's outline rows now say why instead of renaming or closing either one, and a section action refused because the store is locked or invalid says so rather than reporting the project gone. /meta advertises capabilities.outline.
  • Friendly dates parse over HTTP without a write. (#27) GET /api/v1/dates/parse?input=fri%204pm reads date text with the same parser the TUI's date fields use — friendly date, optional wall time, optional trailing zone word (Europe/Berlin, UTC in any case, floating) and fold=later — and answers {"input","date","time":{"local","timezone","fold"}|null,"human"}, where time is exactly the object a PATCH/POST takes as deadline_time/scheduled_time. Text that is not a date is a 200 with {"input","error"}, like /recurrence/explain. /recurrence/explain gains anchor=YYYY-MM-DD to project from a task's own stamp instead of today, and GET /api/v1/lead/explain?input=3%20weeks&anchor=2026-11-01 previews a lead span (canonical, human, and the opens date) before any write. /meta publishes date_order. The CLI twins are tasks due --explain "<text>" (also schedule --explain) and tasks lead --explain "<span>" [--anchor <date>], whose --json is the HTTP data member. The zone/fold word grammar moved out of the TUI into temporal.ParseText, which the CLI's dated verbs now use as well, so tasks due <ref> "fri 4pm Europe/Berlin" works and a zone or fold named both as a flag and as a word is refused. /meta advertises capabilities.date_parse and capabilities.lead_explain. The TUI's date fields now honor the configured date_order — they read 10/2 as October 2 regardless of it before, and mistook a numeric date like 7/15 for a zone name.
  • Link shorthands and title-URL lifting work on every surface. (#30) Expanding a configured link.<name> shorthand (jira:OPS-1234) and lifting a trailing URL out of a capture title into a formal link used to be tasks capture-only. Both now live in the application layer, so POST /api/v1/tasks (links and title), PATCH formal_links, TUI captures, and the CLI apply them identically — a shorthand stores its URL with the token as its default label, and duplicates are judged on the expanded URL. /meta publishes the resolved link_shorthands and link_systems. Title lifting is on by default for HTTP creates, matching the CLI; PATCH never rewrites a title.

Bug Fixes

  • Concurrent formal_links edits no longer overwrite each other. (#31) The stored formal links were not part of a task's revision, so two clients could each PATCH formal_links under the same If-Match and the second silently replaced the first's list. Links are now in the revision's own-field digest and the stale write gets 412 stale_revision. Every stored task's revision changes once on upgrade, so an ETag a client held from before the upgrade must be re-read.
  • Task.depth counts only task ancestors. (#32) Section ancestors above the nearest section were counted too, so every top-level task in a project (a section under "Projects") reported depth: 1 while an Inbox task reported 0. Depth is now the number of task ancestors, matching what parent_id implies.