Skip to content

previous-Zeitreferenzen in Revisionen schreiben #26

Description

@usekaneo

Das Datenmodell sieht previous-Tags vor (NIP-29-Zeitreferenzen auf kürzlich gesehene Events der Gruppe, als Schutz gegen ein Relay, das Ereignisse unterschlägt oder umschreibt). Beim Veröffentlichen einer Revision werden sie nicht gesetzt.

Aufgabe: Beim Signieren die letzten bekannten Event-IDs der Gruppe als previous mitgeben (src/nostr/publish-page.ts), Quelle ist der Space-Store. Beim Lesen zumindest auswerten können.

Quelle: docs/02-data-model-events.md, docs/10-roadmap.md (Doc-Lücken)


Task: uhoz07pa3889p3ndh8fnj5py

Activity

  1. usekaneo commented on Sep 9, 2026

    @usekaneo
    Author
  2. usekaneo commented on Sep 14, 2026

    @usekaneo
    Author

    **** commented:

    Technical refinement

    Status: ready. A small, self-contained change to the publish helpers plus one derived list in the store. The reading side is best-effort, because groups_relay does not verify the tag.

    What exists today

    • TAGS.PREVIOUS = 'previous' is defined (src/nostr/kinds.ts:100), docs/02 describes the tag and NOSTR.md lists it as not written. No code reads or writes it.
    • Publish helpers build their tags themselves: publishRevision (src/nostr/publish-page.ts), publishPlacement (src/nostr/publish-placement.ts), publish-comment.ts, and publishModeration in src/nostr/moderation.ts. Each starts from ['h', groupId].
    • SpaceStore (src/nostr/space-store.ts) sees every event of the group through its four subscriptions (group state by #d, revisions/comments/placements by #h) but keeps no 'recently seen ids' list; apply* only store the parsed objects.
    • docs/08 records that groups_relay does not implement or verify timeline references, so writing them changes nothing on this relay — the value is portability and future gap detection, not enforcement.

    Approach

    1. Track the recent group events. In SpaceStore, maintain a bounded list of the ids of every group event seen (all four subscriptions), newest first by created_at, ties by id, capped (50 is the usual NIP-29 order of magnitude). Expose it as a method (recentEventIds()), not as part of SpaceSnapshot, so publishing can read it without a React prop drilling exercise.
      • Include revisions, placements and comments. Group state (39000-39002) is relay-generated and changes on its own; include it only if it proves useful — recommend leaving it out in the first iteration so the list is stable and about author-visible content.
      • Update on every incoming event, including duplicates (dedupe by id); do not clear it on dropCollected() within the same identity if a publish may happen right after a resubscribe — decide explicitly and test it.
    2. A shared tag builder. Add one helper (e.g. timelineTags(recentIds: string[]): string[][]) in a small module, so all publishers produce the same shape: one ['previous', ...ids] tag, or one tag per id if that proves better against the relay — measure. Wire it into publishRevision, publishPlacement, publishComment and publishModeration. The publish functions take the ids as an input field (previous?: string[]), so they stay network-free and testable; callers read the store.
    3. Callers. PageEditor/NewPageView/HistoryView.restore/useMovePage/Comments/MemberAdmin each call a publish helper; each can obtain getSpaceStore(relayUrl, groupId).recentEventIds() at publish time. Prefer a tiny withTimeline(relayUrl, groupId) wrapper over touching every call site's signature.
    4. Reading side (best effort). Parse previous in parseRevision and keep it on the revision (and on comments/placements if cheap). Then a light diagnostic: if a previous id is not among the events the client has seen, the relay may have withheld something. Surface it only as a subtle 'history may be incomplete' hint, never as a hard error — false positives are expected, because the union of 'recently seen' differs per client and per session.
    5. Measure against the relay first. Confirm that groups_relay accepts a 1818 carrying previous (it should, since it does not verify it) and that the event is stored and served back intact. If the relay strips it, the write side is still worth doing for foreign relays; the reading side then simply never sees it locally.

    Subtasks

    • SpaceStore.recentEventIds() with cap, ordering and lifecycle rules
    • timelineTags helper + previous input on publishRevision, publishPlacement, publishComment, publishModeration
    • Callers read the store at publish time (thin wrapper preferred)
    • Parse and retain previous; optional gap hint
    • Relay measurement (accepted, stored, served back intact)
    • Tests: list ordering/cap/dedupe, every publisher emits the tag, parse round-trip
    • Docs: docs/02 ('not implemented yet' -> written), docs/09 completeness note, NOSTR.md tag table, docs/10 doc-gap row

    Acceptance criteria

    • Every 1818, 31818, 1111 and moderation event the app publishes carries a previous tag listing the most recent group events the client had seen.
    • The list is bounded and deterministic; it never grows without limit and never contains duplicates.
    • The app still publishes (with an empty or absent tag) when the store has seen nothing yet — a publish must not be blocked by this feature.
    • Incoming previous values are parsed and do not break anything when absent or malformed.

    Risks / open decisions

    • groups_relay ignores the tag, so this is groundwork, not a security fix. Do not present it as making withholding detectable on the current relay.
    • Gap detection is heuristic (docs/09 already says so): different clients have different 'recently seen' sets, so a missing id is not proof of censorship. Keep it a hint.
    • Event size: 50 ids per event is a few KB; consider a smaller cap if it shows up in relay limits or storage.
    • Ordering by created_at is manipulable (docs/09); the tag is informational, and parent-rev remains the real order.
    • A stale list after a reconnect is acceptable for a reference tag, but must not be presented as exhaustive.

    Depends on / relates to

    CON-8 (cache may make the seen-set larger and more useful), CON-10/11 (deletion/tombstone events also belong in the timeline), docs/09 (completeness), docs/08 (relay ignores the tag).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions