NIPs by PolleramaCommunity NIPs, surfaced by trustConnect
npub14ac075fuerm...

Timeline Cards

Published Aug 10, 2026

Timeline Cards

draft optional

A timeline card is an addressable event placing one dated fact on a shared timeline. Cards carrying the same collection label form a collection; any key may publish into any collection, and no key may exclude another. Discovery is a marker in the indexed t tag, plus query shadows: self-labels for date and jurisdiction, a g tag for geohash.

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.

Motivation

Two problems recur wherever events are recorded permissionlessly.

An open discovery marker is squattable. This NIP splits discovery from membership: a marker for the relay to filter on, and a shape test the client applies to what comes back.

A client that does not implement a spec silently drops the tags it does not know, so declared state fails to travel. This NIP declares no state a card's meaning depends on: the load-bearing signal is a field's presence or absence, which no client can drop, and a dropped marker (fork, adopt, adapt) leaves an ordinary card.

Event kind

A timeline card is kind 30828, an addressable event as defined in [NIP-01](01.md). The event is self-labeling per [NIP-32](32.md): every field is a tag on the card itself. A client MUST ignore external kind 1985 labeling events when applying this NIP — a card's membership and axes are read from the card alone, so that no third party can attach a card to a collection its author did not choose.

content is djot. Clients that do not implement djot SHOULD render content as plain text rather than as another markup language.

Required tags

TagExampleMeaning
d["d","proposition-number-assigned"]addressable identifier; republishing under the same d replaces
event_date["event_date","2026-07-01"]the date the card is about, YYYY-MM-DD
marker["t","wikitimechain"]discovery marker; see Discovery
collection["L","timeline.collection"] ["l","example-timeline","timeline.collection"]which timeline this card belongs to
date buckets["L","timeline.date"] ["l","2026","timeline.date"] ["l","2026-07","timeline.date"]query shadow of event_date

event_date MUST be a full YYYY-MM-DD date and is the card's position on the timeline. It is distinct from the event's created_at, which records when this version was signed, and from the OPTIONAL published_at; see Optional tags.

**d is opaque.** How a publisher mints it is their business; a client MUST NOT derive meaning from it, whatever it appears to encode.

A card MUST carry exactly one collection label. Its value is the collection's identifier and SHOULD be lowercase kebab-case.

Buckets exist because multi-letter tags are not indexed by relays. A card MUST emit a YYYY bucket, and MUST emit a YYYY-MM bucket when the month is known. Buckets MUST encode only known precision. Where the true date is coarser than a day, the placeholder components of event_date MUST NOT be emitted as buckets — a card known only to the year emits no month bucket.

Location

A card describing an event with a location SHOULD carry a jurisdiction ladder. If it does, the ladder MUST run from the top down to the event's true scope, with every rung present.

["L", "ISO-3166-1"],            ["l", "JP", "ISO-3166-1"],
["L", "ISO-3166-2"],            ["l", "JP-13", "ISO-3166-2"],
["L", "timeline.location"],     ["l", "jp-13-shibuya", "timeline.location"]

Relays filter on a label's value, not on the (L,l) pair, so an omitted rung is not queryable even when a deeper rung implies it. That is why the whole ladder is written out rather than derived.

  • ISO rungs MUST use ISO codes verbatim under the standard namespaces ISO-3166-1 and

ISO-3166-2.

  • Rungs below the ISO-3166-2 level are namespaced timeline.location and SHOULD be

lowercase kebab-case, prefixed by their parent rung.

  • The ladder MUST NOT extend deeper than the event's real scope. A national act stops

at the country rung.

A collection whose events have no location is valid and carries no ladder.

Optional tags

TagExampleRule
g["g","xn76urx6"] ["g","xn76"] ["g","xn7"]geohash, point events only. Emitted as prefix rungs for proximity queries. MUST NOT be a jurisdiction's centroid.
t["t","taproot"]freeform topic. Unspecified by design: no registry, no controlled vocabulary.
published_at["published_at","1784681375"]original publication time as in [NIP-23](23.md). SHOULD be preserved across replacements of the same d, while created_at changes with each.
event_time["event_time","14:30"]UTC time of day refining event_date. HH:MM or HH:MM:SS, 24-hour. MUST encode only known precision. Emits no bucket and MUST NOT affect discovery or dedup.

Text fields

Three fields carry a card's text, a gradient of length:

FieldExampleRole
title tag["title","Proposition number assigned"]the shortest — names the card
summary tag["summary","The vendor shipped patched firmware for every affected model."]the event in plain words, in the publisher's voice; SHOULD be one or two sentences
contentdjot bodythe longest — the full statement, where links and sources live

None is required. A card SHOULD carry at least one; a card carrying none is valid and says nothing. When summary and content are both present, summary MUST NOT assert anything content does not support.

All three are human-readable free text. A client MUST NOT parse any of them for machine-readable state, and MUST NOT require any particular wording or form. The only machine-readable signal these fields carry is presence or absence; see Motivation.

What a given combination means — a summary standing alone, a bare title — is vocabulary, and vocabulary belongs to a corpus, not to this NIP. A publisher MAY build conventions on these shapes; a conforming client MAY surface a shape distinctly, but MUST derive it from presence and absence alone, never from wording. Declared states such as corroborated or disputed are deliberately absent: no publisher can credibly make such a claim about their own card. Disagreement is expressed by a second key publishing the same d.

A client MAY render every field a card carries, or use the gradient as tiers of disclosure — title in a list, summary in a preview, content on open. A client rendering a subset MUST fall back to the fields the card does carry, so that no card carrying any text renders blank.

Filling a card in under its own d — adding the content a summary anticipated — is an ordinary replacement per [NIP-01](01.md), not a revision: no fork marker is involved, because the recorded event never changed. A publisher MUST NOT reuse a d for an unrelated card: reactions and comments address 30828:<pubkey>:<d> and would silently reattach.

Revisions

A card MAY carry a revision of a document that another card records. Such a card MUST tag its parent with a fork marker in both forms:

["a", "30828:<pubkey>:<parent-d>", "", "fork"],
["e", "<event id of the version revised>", "", "fork"]

The third element is the relay-hint slot and MUST be present, empty if unknown; the marker is the fourth element. Both tags are REQUIRED: the a coordinate survives an edit of the parent, while the e id pins the exact version this card was built against.

The change itself is expressed with djot's insert and delete syntax, {-removed-} and {+added+}. A client MUST NOT render a card as a diff unless it carries both a fork marker and marks in content; either alone is an ordinary card.

A marked card asserts two documents, and both MUST be recoverable from it: stripping the deletions and unwrapping the insertions yields the newer document exactly, and the converse yields the older. A publisher SHOULD verify a marked card by performing both reconstructions rather than by reading it.

Adoption and adaptation

A card MAY cite another card as its source. Two markers, in the slot the fork marker uses:

  • adopt — the card carries the source's content verbatim. A card MUST NOT

carry more than one adopt.

  • adapt — the card's content is its own, built on the source. A card MAY carry

many.

Each source is cited with three tags, all REQUIRED, carrying the same marker:

["a", "30828:<pubkey>:<source-d>", "", "adopt"],
["e", "<event id of the version carried>", "", "adopt"],
["p", "<pubkey of the source's author>", "", "adopt"]

The a coordinate survives replacement of the source, the e id pins the exact version cited, and the p tag routes credit. The adopting card keeps its own d; a shared d under a different key remains the rival-version case, not adoption.

**The e id is a commitment, not a link.** A client MUST NOT dereference it: no fetching the source, no version comparison, no derived "since edited" state.

An adopting card is an ordinary card. It MUST pass the membership gate on its own tags, and it deduplicates, replaces and displays like any other.

A reaction ([NIP-25](25.md)) is not an adoption. Citing a source requires publishing a card.

A client MAY render an attribution line from these tags (for example via <npub> · adopted), derived at read time.

Discovery

A client discovers cards with a single indexed filter (the limit is illustrative):

{"kinds": [30828], "#t": ["<marker>"], "limit": 500}

The marker names a corpus, not this protocol. It is a lowercase single-word t value chosen by whoever starts one; wikitimechain is the marker of the first deployed corpus. A card MUST carry the marker of the corpus it belongs to, and a client is configured with the marker or markers it reads. A marker is permanent in practice, since changing it requires re-signing every card.

**The marker and freeform topics share the t tag.** A card MAY therefore carry many t values, and nothing distinguishes a marker from a topic by inspection. Two consequences, both intended:

  • A client MUST test for the presence of its corpus marker among a card's t values,

and MUST NOT assume a card carries only one.

  • A topic value on an unrelated card may coincide with a corpus marker, and that card

will be returned by the corpus's discovery filter. This is not a defect to be patched in the tag layout; it is precisely what the membership gate below exists to absorb.

The membership gate

A marker is discovery bait, not proof. Any key may wear any marker. A client MUST apply the following test to every event it receives, whatever the source, and MUST discard events that fail it:

the marker, and a parseable YYYY-MM-DD event_date, and a timeline.collection label.

The collection identifier is read from that label's value.

Labels are indexed by value only

Relays index a label by its value; the namespace (the L tag, and the label's third element) is not part of the filter. {"#l":["2026"]} matches the value 2026 in any namespace, including an unrelated one.

Implementations MUST NOT substitute an #l filter for the membership gate. Value grammars in this NIP (ISO codes, kebab identifiers, YYYY and YYYY-MM dates) are kept mutually distinguishable to make collisions unlikely, but they are not a guarantee and MUST NOT be relied on as one.

Axes do not compose

Collection, date and location are all l values. A single filter's #l array is an OR, and two constraints sharing the key l cannot be AND-ed in one REQ. A client requiring more than one axis MUST fetch on the most selective one and filter the remainder locally. Implementations MUST NOT assume server-side multi-axis AND.

Exact membership of one collection is:

{"kinds": [30828], "#l": ["<collection>"]}

whose results MUST be re-checked against the gate.

Client behavior

  • Cards are deduplicated by the addressable-event rules of [NIP-01](01.md) — newest

created_at per (kind, pubkey, d) — which, since this NIP defines a single kind, is a key of pubkey and d.

  • Cards from different pubkeys sharing a d MUST NOT be collapsed. They are rival

versions of one entry and a client SHOULD present them as such rather than choosing between them silently.

  • A card's displayed date MUST be event_date. Date buckets are query shadows and MUST

NOT be used for display.

  • Inbound citations of a card are queried with #a, #e or #p filters, and the

results MUST be filtered to events carrying an adopt or adapt marker — a bare #p match is an ordinary mention. Any reputation derived from these edges is computed at read time; no score is stored or declared.

Marginalia is addressed by kind

Reactions ([NIP-25](25.md)) and comments ([NIP-22](22.md)) address a card at 30828:<pubkey>:<d>. Republishing the same content under a different kind changes the address and silently orphans every existing reaction and comment.

Publishing

A publisher signs a kind 30828 with the required tags and sends it to any relay. A collection exists once a card carries its identifier; there is no registry. What a client chooses to show is outside this NIP.

Security considerations

The marker is squattable (see The membership gate). The gate discards an unusable card on shape; it cannot establish authorship or good faith, and it is not intended to.

There is no author allowlist. Any key passing the gate is admitted, which is the point of the scheme and also its cost: junk and forgeries pass a shape test as readily as records do. Clients expecting adversarial input SHOULD rank rather than gate, using signals outside this NIP such as the reader's own follow graph. Reaction and citation counts MUST NOT be treated as authority: keys are free to mint.

Citation edges are self-asserted. A key may adopt its own cards under other keys; copying without an adopt tag is undetectable. A cited e id may reference a version no relay still holds; a dangling id is not an error.

Self-asserted values MUST NOT be treated as evidence. created_at, published_at, event_date and event_time are all written by the signer and none is attested.

Example

A region-scoped card, with no local rung and no geohash:

{
  "kind": 30828,
  "content": "Over the summer the elections authority assigns the measure its proposition number for the November ballot.\n\nSource: [Elections authority — ballot measures](https://example.org/ballot-measures)",
  "tags": [
    ["d", "proposition-number-assigned"],
    ["title", "Proposition number assigned"],
    ["published_at", "1784681375"],
    ["event_date", "2026-07-01"],
    ["event_time", "14:30"],
    ["t", "wikitimechain"],
    ["L", "timeline.collection"],
    ["l", "example-timeline", "timeline.collection"],
    ["L", "timeline.date"],
    ["l", "2026", "timeline.date"],
    ["l", "2026-07", "timeline.date"],
    ["L", "ISO-3166-1"],
    ["l", "JP", "ISO-3166-1"],
    ["L", "ISO-3166-2"],
    ["l", "JP-13", "ISO-3166-2"]
  ]
}