Skip to content

CON-10: request deletion of one's own revision (NIP-09 kind 5) - #83

Closed
molgerz wants to merge 5 commits into
mainfrom
feat/con-10-delete-own-revision
Closed

molgerz wants to merge 5 commits into
mainfrom
feat/con-10-delete-own-revision

Conversation

@molgerz

@molgerz molgerz commented Sep 14, 2026 •

Copy link
Copy Markdown
Owner

Rejected — not merged. CON-10 was rejected on 2026-09-16 as a product decision: being able to delete a single revision of a page is not a feature Akasha needs. The branch feat/con-10-delete-own-revision (head c702dec) is kept on purpose so this can be picked up again — reopen this PR or branch from that commit. Everything below describes the work as it stood when it was closed.

One known defect, from the UI test pass: the kind 5 carries no d tag, so after a reload the removal cannot be attributed to a page and the explanatory callout in step 5 disappears (step 6 falls with it). That is one tag on the event, not a design problem.


Closes CON-10.

A writer can ask the relay to remove one of their own revisions (NIP-09, kind 5), and the history explains what that did and did not achieve.

What is in it

  • SpaceStore reads kind 5 for the group on a subscription of its own, keeps the parsed requests and derives the removed set on every rebuild. Nothing is dropped from the store: the request is a tombstone, so a reload re-derives the removal from the relay instead of trusting local state.
  • The gate the relay does not provide is ours — a request only counts against the requester's own revision.
  • buildPages reconnects the chain around a removed revision instead of tearing it: a survivor whose parent-rev names a removed revision is re-pointed at the nearest surviving ancestor. A removed merge's other ancestors are marked as referenced rather than made parents, so they do not come back as leaves and no successor wears a merge badge for a merge nobody made.
  • parseDeletion requires the h tag naming this group. The subscription asks for #h anyway, so tolerating its absence only left room for a relay to hand us a request from another group.
  • DELETION_REQUEST is declared in APP_CONTENT_KINDS, for relays that enforce a group's supported_kinds.

How to test

Local relay and seeded group: ./scripts/dev-relay-up.sh, ./scripts/dev-group-seed.sh, then npm run dev.

  1. Write two or three revisions of a page as user A. Open History.
  2. "Request deletion" appears only on A's own revisions. Sign in as B: the button is gone from A's revisions; signed out it is gone entirely.
  3. Request the deletion of a middle revision. The confirmation and the result call it a request, never "deleted", and name the copies that may remain elsewhere.
  4. The revision disappears from the list, the ones around it stay connected (no torn chain, no second root), and a muted callout names how many revisions of this page were removed by their authors.
  5. Reload the page — the removal is still there, derived from the relay rather than remembered locally.
  6. Remove the last remaining revision of a page: the page itself disappears, and the history still explains why ("No revisions left for this slug." plus the callout). This is the case that used to leave a reader with a bare "No revisions for this slug."
  7. Decline the confirmation dialog: nothing is published.

Automated: npm run typecheck && npm run lint && npm test && npm run build — green.

Stack

Base for [CON-11]. Merge this one first.

M added 5 commits September 12, 2026 17:26
Add the NIP-09 e/k tag names to the single home of kind and tag constants, a
parser for kind 5 requests, and a signer helper that publishes one for a page
revision.

The parser only accepts a request about our page revisions in this group: the
h tag, when present, must match, and a foreign k is rejected. The h tag is
written on publish so the request is scoped to the group and can be read back
with the same filter as the content.

NIP-09 is a request, not a guarantee: docs/09-security-privacy.md.
buildPages takes the set of revision ids a NIP-09 request removed. They are
dropped from the exposed list and from the leaves, and the survivors are
reconnected around them: a survivor whose parent-rev names a removed revision
is re-pointed at the nearest surviving ancestor, first parent first, with a
cycle guard. The parent references of a removed revision are no longer
collected, because the repair already keeps its parents from looking like
leaves again.

Blame and findCommonAncestor need no change: they are fed the repaired list,
so the walk crosses the gap on the bridged predecessor. A page whose only
revision was removed disappears instead of showing a removed head.
SpaceStore reads kind 5 for the group on a fifth subscription, keeps the
parsed requests and derives the removed set on every rebuild. The gate the
relay does not provide is ours: a request counts only against the requester's
own revision. Nothing is removed from the store — the request is a tombstone,
so the chain can bridge across it and a reload re-derives the removal from
the relay.

HistoryView offers "Request deletion" only on one's own revisions, next to
the admin 9005 button. The confirm and result text call it a request, name
the copy that may remain on other relays and clients, and never say
"deleted". A muted note lists the revisions of the page that were removed by
their authors.

Test maintenance beyond the ticket, both needed to keep the gate green on a
machine where the local dev relays are running:
- the CON-36 reconnect test expects the fifth subscription now;
- the tests that render a mention or run the live editor (editor-complete,
  formatting-help, markdown-live, Markdown, task-marker, markdown-flavour)
  prime the profile cache, which opened a real WebSocket to the configured
  profile relays during tests and made vitest fail intermittently with an
  undici Event-realm error. The profile store is mocked there; none of those
  tests asserts a fetched name.
# Conflicts:
#	src/ui/Markdown.test.tsx
#	src/ui/markdown-live.test.ts
… reachable

`parseDeletion` tolerated a missing `h` tag on the grounds that NIP-09 does not
define one. That was the wrong way round: the subscription asks for `#h`, so a
request without it never reaches this app at all, while accepting one anyway
let a relay hand us a request from a foreign group and have it remove a
revision in this one. The tag is required now, and the check stays here rather
than being trusted from the filter.

`DELETION_REQUEST` joins `APP_CONTENT_KINDS`, so a relay that enforces a
group's `supported_kinds` does not reject the one event whose whole point is to
be accepted. Our own relay enforces nothing here; this is a declaration for the
ones that do.

Repairing the chain around a removed revision followed only the first-parent
path, so a removed *merge* left its other ancestors with nothing pointing at
them and they came back as leaves — a fork out of a deletion. They are
collected as `bridged` and counted as referenced instead of being made parents:
a revision's arity is what its author signed, and a successor that inherited
two parents would wear a merge badge for a merge nobody made.

The history view now explains the removal in both of its branches. Removing the
last revision of a page makes the page itself disappear, which is exactly the
moment the reader needs to be told why — so the callouts are computed off the
route's slug and drawn above "No revisions left for this slug." as well.
@molgerz

molgerz commented Sep 16, 2026

Copy link
Copy Markdown
Owner Author

Closing unmerged: CON-10 is rejected as a product decision — being able to delete a single revision of a page is not a feature Akasha needs right now. Not a verdict on the branch, which is green on typecheck, lint, tests and build; the one open defect from the UI pass (the kind 5 carries no d tag, so after a reload the removal cannot be attributed to a page) is a one-tag fix.

The branch feat/con-10-delete-own-revision stays untouched, so this can be reopened if the decision ever changes.

Note for #87 and #88: both are stacked on this branch and carry commits 098cbc4, 0a199cf and 5c8390f, so merging either as it stands would land the rejected feature anyway. See the CON-10 ticket for the two options.

@molgerz molgerz closed this Sep 16, 2026
@molgerz molgerz added status:rejected wontfix This will not be worked on decision labels Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

decision status:rejected wontfix This will not be worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant