Built-in and user-defined views
On this page
A view is a named query: a label bound to a piece of TQ source text that resolves to a set of tasks when you call (query "name"). cairn ships ten built-in views, expressed in TQ itself. They are the language's shipped vocabulary — plan-frontier, leaf-tasks, stale-phases, and the rest — and you read them on the read surface through task_query or define your own through task_query_write.
Each built-in is data, not tool surface. The view's body is ordinary TQ, so you can read the definition with (-> (views) (:where (= :origin "builtin")) (:select :source)), copy it, and adapt it. A view defined under your own name shadows the built-in of the same name; remove the user view and the built-in reappears.
Reading a view
Resolve any view by name with the (query "name") source:
task_query (query "plan-frontier")
The result renders as a task list — one line per task, slug first, then display name, status, and any counts the view enriched:
2 tasks:
- 2026-06-20-write-the-parser (open) obs=3 edges=2
- 2026-06-20-wire-the-cli (open) obs=1 edges=1
(query "name") is a read. A view defined on the write surface still resolves on the read surface, because reading a view is a read regardless of how the view was created. Naming a view that does not exist is a structured error that enumerates every resolvable name — both built-in and user-defined:
Query error: Unknown named query plan-fronteir. Available: active-roots, orphans, leaf-tasks, stale-phases, plan, plan-frontier, frontier, recent, busy, hub-tasks, knowledge.
The ten built-in views
The table lists every shipped view with its TQ source text exactly as cairn defines it. frontier is an alias — its body is (query "plan-frontier") — so the eleven accepted names resolve to ten distinct queries.
| View | TQ source | Yields |
|---|---|---|
active-roots |
(-> (active) (:where (not (has :parent)))) | Live tasks with no parent — the roots of every open plan. |
orphans |
(-> (all) (:where (= :edge-count 0))) | Tasks with no edges in any direction: unparented, unlinked, unforked. |
leaf-tasks |
(-> (active) (:where (= (count (:follow :phase-of)) 0))) | Live tasks with no phase children — the bottom of the plan tree. |
stale-phases |
(-> (active) (:where (any (:back :phase-of) (or (= :status "completed") (= :status "abandoned"))))) | Live phases whose parent has already settled, a sign the plan moved on without them. |
plan |
(-> (current) (:follow :phase-of) (:or-else (-> (current) (:back :phase-of) (:follow :phase-of))) (:enrich)) | The phases of the current task's plan, enriched with counts. Current-scoped. |
plan-frontier |
(-> (query "plan") (:where (and (not (or (= :status "completed") (= :status "abandoned"))) (all (:follow :depends-on) (or (= :status "completed") (= :status "abandoned")))))) |
The ready subset of plan: unsettled phases whose every depends-on dependency is settled. |
frontier |
(query "plan-frontier") |
Alias for plan-frontier. |
recent |
(-> (active) (:sort :updated-ts) (:take 20)) | The twenty most recently touched live tasks. |
busy |
(-> (active) (:sort :obs-count) (:take 20)) | The twenty live tasks with the most observations. |
hub-tasks |
(-> (active) (:sort :edge-count) (:take 20)) | The twenty live tasks with the most edges — the connectors in the graph. |
knowledge |
(-> (current) (:closure :phase-of :depends-on :related) (:enrich)) | The transitive neighborhood of the current task across all three edge types, cycle-safe and depth-bounded. |
Three of these views read the (current) source — plan, frontier/plan-frontier, and knowledge — so they resolve against the current task pointer and error when no task is selected. The rest range over (active) or (all) and resolve the same way in every session. The TQ sources table defines each producer.
Built-in source forms in TQ
Each built-in is a complete TQ program, and every piece it draws on is documented in the TQ language reference. The sources are (active), (all), and (current). The steps are (:where), (:follow), (:back), (:sort), (:take), (:or-else), (:closure), and (:enrich). Inside a predicate they reach across edges with the (any TRAV P) and (all TRAV P) quantifiers and count with the (count TRAV) field-expression. plan-frontier composes over plan by name, and frontier composes over plan-frontier by name, so a view defined in terms of another view resolves transitively.
A view defined in terms of itself is a structured error, not an unbounded loop. Define a view named self whose body is (query "self") and resolving it reports the cycle rather than recursing — self in the error text is the offending view name:
Query error: View self is defined in terms of itself.
recent is a view, not a source
recent is a view name, so you read it with (query "recent"). (recent) is not a source form. The query language has exactly ten sources — (all), (active), (dormant), (current), (node "substr"), (query "name"), (schema), (views), (fields), (edges) — and recent is not among them. Calling (recent) as if it were a source is an unknown-source error that lists the ten known sources:
Query error: Unknown source recent; known: active, all, current, dormant, edges, fields, node, query, schema, views.
The same holds for every other view name: (plan), (orphans), and (busy) are not sources. Reach a view only through (query "name").
Defining your own views
define! and undefine! are write forms — they record an event and are accepted only on the task_query_write surface. The read surface refuses them:
Query error: Mutations need the task_query_write surface; task_query is read-only.
define!
task_query_write (define! "blocked-phases" (-> (active) (:where (= :status "blocked"))))
(define! "name" Q) validates Q read-only, records its source text as a view.define event, and returns Q's tasks — so define! is interchangeable with the query it names. The view name must be a non-empty string. The query Q is evaluated read-only even on the write surface: you define over a selection, you do not mutate inside the definition. After the call, (query "blocked-phases") resolves on either surface.
undefine!
task_query_write (undefine! "blocked-phases")
(undefine! "name") records a view.undefine event and returns no tasks. It removes the user view of that name. If a built-in of the same name exists, that built-in becomes visible again.
Shadowing a built-in
A user view of a built-in's name shadows it in the one resolver. Define orphans over (dormant), and (query "orphans") now returns settled tasks instead of edgeless ones; undefine! it and the built-in (-> (all) (:where (= :edge-count 0))) resolves again:
task_query_write (define! "orphans" (dormant))
task_query (query "orphans") # now resolves to your definition
task_query_write (undefine! "orphans")
task_query (query "orphans") # the built-in is back
A view is a fold of view.define/view.undefine events, so a rebuild reconstructs your views exactly. See events, projection, and reconcile for how the view vocabulary replays from the durable log.
Listing every view
The (views) source produces every resolvable view as a queryable node-set. Each view node carries an :origin of builtin or user and a :source field holding its TQ text, so you can filter and project the catalogue with the algebra itself:
task_query (-> (views) (:where (= :origin "builtin")) (:ids))
task_query (-> (views) (:where (= :origin "user")) (:select :source))
A built-in shadowed by a user view of the same name reports once, as user — (views) reflects what resolves, not what ships.
Related
- TQ language — the sources, steps, and predicates every view is written in.
- Plans, phases, and the frontier — why the frontier is the ready subset of a plan.
- Tools — the
task_queryandtask_query_writewire surface.