Files
projax/store/task.go
mAi 0dfa0e2ab7 feat(store): Phase 7b — uniform Task shape + mBrian-native task read/write
Adds the first-class task layer the Phase 7 design (docs/plans/phase-7-entity-model.md)
calls for, on the mBrian backend:

- store.Task: one uniform view-shape, two sources (CalDAV VTODO | mBrian
  type=['task'] node); TaskReader/TaskWriter capability interfaces (separate
  from ItemReader/ItemWriter — only the mBrian backend has task nodes).
- MBrianReader.TasksForItem: materialises type=['task'] child_of nodes into
  Task, created-at order (Q5).
- Task exclusion at the reader chokepoint: nodeIsTask filters tasks out of
  ListAll/Roots/MaiOrphans/Search/AllTags, so they never leak into ANY project
  surface (tree/graph/dashboard/calendar/timeline + MCP list/tree, which all
  funnel through ListAll/ListByFilters) — implements Q6's intent in one place
  instead of per-surface. GetByID/GetByPath still resolve a task node.
- MBrianWriter task writes: CreateTask (POST type:'task' + slug + child_of
  edge), SetTaskStatus (done=status, Q2), SetTaskDue, EditTaskTitle, DeleteTask.
  Uses the now-live POST /api/projax/nodes 'type' field.
- Item.Render (metadata.projax.render) + RendersChecklist() for compact
  checklist mode (Q1); round-tripped via UpdateInput.Render.

Unit-tested: nodeIsTask, taskFromNode, dueToJSON, RendersChecklist.
Pre-existing TestParityListAll drift (store=65 vs mbrian=66) is unrelated and
tracked separately by head.
2026-06-01 17:50:48 +02:00

92 lines
3.9 KiB
Go

package store
import (
"context"
"time"
)
// Phase 7 — Task is the uniform view-shape for a unit of work attached to a
// projax item, materialised from EITHER a CalDAV VTODO or an mBrian
// type=['task'] node. One shape, two sources; writes dispatch on Source
// (design §3.3, the slice-B "one Item shape, two backends" pattern applied
// to tasks). Consumers (the detail Tasks section, future dashboard/timeline
// rollups) render the uniform shape and don't care which backend produced it.
type Task struct {
// ID is a stable per-source identifier: the mBrian node uuid for an
// mBrian-native task, the VTODO UID for a CalDAV task. Unique within a
// source; templates key rows on it.
ID string
Title string
Done bool
Due *time.Time
// Source is "mbrian" or "caldav" — write handlers dispatch on it.
Source string
// Status is the raw lifecycle status for mBrian tasks (active|done|
// archived). For CalDAV tasks it carries the VTODO STATUS verbatim
// (NEEDS-ACTION|IN-PROCESS|COMPLETED|CANCELLED) for callers that want it;
// Done is the normalised boolean either way.
Status string
// ParentItemID is the projax item this task hangs under.
ParentItemID string
CreatedAt time.Time
// --- mBrian-source handle (Source == TaskSourceMBrian) ---
// NodeID is the mBrian node uuid (== ID); the write API targets it.
NodeID string
Slug string
// --- CalDAV-source handle (Source == TaskSourceCalDAV) ---
// CalendarURL + UID address the VTODO for ETag-guarded writeback via the
// existing caldav write path.
CalendarURL string
UID string
}
// Task source discriminators.
const (
TaskSourceMBrian = "mbrian"
TaskSourceCalDAV = "caldav"
)
// TaskCreateInput captures the editable surface of a new mBrian-native task.
// CalDAV tasks are created through the existing CalDAV write path (VTODO PUT),
// not this shape — only the mBrian backend creates task nodes.
type TaskCreateInput struct {
Title string
Slug string
ParentItemID string // the project (or task) this task attaches to via child_of
Due *time.Time // optional
}
// TaskReader is the read-path contract for mBrian-native task nodes. It is a
// capability SEPARATE from ItemReader: only the mBrian backend has task nodes,
// so the legacy *Store does not implement it. Web handlers obtain it via a
// type-assertion on the active Items backend (see Server.taskBackend).
type TaskReader interface {
// TasksForItem returns the mBrian-native tasks (type=['task'] nodes)
// attached to itemID via a child_of edge, in created-at order (Q5 —
// created order only; no manual reorder in v1).
TasksForItem(ctx context.Context, itemID string) ([]*Task, error)
}
// TaskWriter is the write-path contract for mBrian-native task nodes. Twin of
// TaskReader; implemented only by the mBrian backend (*MBrianWriter). Writes
// funnel through mBrian's scoped /api/projax HTTP surface so projax-created
// task nodes are byte-identical to UI/MCP/migration nodes (the slice-C
// discipline). Delete reuses the node soft-delete the API already exposes.
type TaskWriter interface {
// CreateTask POSTs a type=['task'] node (slug honored, metadata.projax
// carrying status/due) then attaches it to ParentItemID via a child_of
// edge, and returns the materialised Task.
CreateTask(ctx context.Context, in TaskCreateInput) (*Task, error)
// SetTaskStatus PATCHes metadata.projax.status (done|active|archived) —
// task done-state reuses the existing lifecycle (Q2), no separate field.
SetTaskStatus(ctx context.Context, nodeID, status string) error
// SetTaskDue PATCHes metadata.projax.due; a nil due clears it.
SetTaskDue(ctx context.Context, nodeID string, due *time.Time) error
// EditTaskTitle PATCHes the node title.
EditTaskTitle(ctx context.Context, nodeID, title string) error
// DeleteTask soft-deletes the task node.
DeleteTask(ctx context.Context, nodeID string) error
}