[idea:tag-index-reference]reference2026-08-20

#tag-index

A declared projection of tag values onto an #ideas row. It exists because a tag’s value is arbitrary and a row’s fields must be encodable; a projection makes the value narrow and checked, which is what lets it ride a row at all.

#import "@rookery/core:0.1.0": ideas, tag-index

#let INDEX = tag-index((
  cycle: (family: "cycle-"),                      // -> "26-27"
  kind: (family: "venue-", one-of: KINDS),        // -> "postdoc"
  deadline: (key: "date-deadline", stamp: true),  // -> "20261101"
  stage: (from: stage-of),                        // derived
))

#context {
  for e in ideas(index: INDEX) [- #e.kind, due #e.deadline]
}

Each field names exactly one extractor:

FormWhat it projects
key: "<tag>"That tag’s value, or none where the idea does not carry it.
family: "<prefix>"The first flat tag whose key starts with the prefix, prefix stripped. one-of: restricts and orders the candidates, so an idea carrying two of a family resolves to the earliest you listed rather than to whichever key order happens to yield first.
from: <function>Called with the idea’s whole tag dictionary. A derived value — the current stage of a dated log, how far something got — is a computation rather than a tag value, and this is the only form that makes one filterable or sortable.

Any of the three may carry stamp: true, which renders a datetime as a zero-padded [year][month][day] string. That is not only a display choice: a fixed-width numeric string sorts lexically in date order, so a stamped field is a free sort key.

A projected value must be a scalar — a string, an integer, a float, a boolean or none — and the build fails where one is not. A field may not take the name of a row field above, for the same reason: naming one href and silently replacing every link on the page is the failure this refusal prevents.