Tag a comment into the room the event it answers lives in (#497)

This commit is contained in:
Coracle-Bot 2026-09-11 00:32:41 +00:00 committed by hodlbod
parent e052191bb2
commit ad480ad4a0
4 changed files with 39 additions and 1 deletions

View file

@ -654,6 +654,8 @@ Acceptance:
top-level comment.
- Alice sees both and can add her own comment at the root, optionally attaching
an image to it.
- Every comment on an article posted in a room is tagged into that room, so the
relay handles it as part of the group.
### US-040 — React to a post with an emoji
@ -706,6 +708,8 @@ Acceptance:
appends a post and increments the reply count in the header.
- "Reply" on a single post shows "Replying to" that post, which can be cleared
before submitting.
- A reply to a thread in a room is tagged into that room, so the relay handles
it as part of the group.
- The thread's opening post carries an "OP" badge on every page.
### US-044 — Navigate a long thread

View file

@ -1,6 +1,6 @@
import * as nip19 from "nostr-tools/nip19"
import {HOUR, MINUTE} from "@welshman/lib"
import {LONG_FORM, MESSAGE, NOTE, makeEvent} from "@welshman/util"
import {COMMENT, LONG_FORM, MESSAGE, NOTE, makeEvent, tagSpec, tagValue} from "@welshman/util"
import type {SignedEvent} from "@welshman/util"
import {Article, Comment, Thread} from "@welshman/domain"
import type {Locator, Page} from "@playwright/test"
@ -325,6 +325,7 @@ test("US-039 comment on an article", async ({seed, as}) => {
space
.kind(Article)
.writer()
.setRoom(space.url, "general")
.setIdentifier("tending-the-garden")
.setTitle("Tending the Garden")
.setSummary("A short teaser about gardens.")
@ -349,6 +350,15 @@ test("US-039 comment on an article", async ({seed, as}) => {
// relay confirms it, so for a moment the page carries this text twice. Match the rendered comment.
await expect(comment(bob, "The soil chapter is the good one.")).toBeVisible()
// A comment on a room event is a room event, so it carries the room the root lives in. Without
// that tag the relay doesn't see it as part of the group, and neither its access rules nor a
// room deletion ever reach it.
await expect
.poll(() =>
getPublishedEvents(bob.context(), COMMENT).map(event => tagValue(tagSpec("h"), event.tags)),
)
.toEqual(["general"])
const carol = await as(users.carol, articlesPath)
await openArticle(carol, "Tending the Garden")
@ -747,6 +757,13 @@ test("US-043 reply to a thread and to a specific post", async ({seed, as}) => {
await expect(bob.getByText("21 replies")).toBeVisible()
// The same for a thread reply, which goes out through a different composer.
await expect
.poll(() =>
getPublishedEvents(bob.context(), COMMENT).map(event => tagValue(tagSpec("h"), event.tags)),
)
.toEqual(["lounge"])
// The thread's author is marked OP wherever their posts turn up, second page included.
await bob.getByRole("button", {name: "2", exact: true}).click()
await expect(bob.getByText("Page 2 of 2")).toBeVisible()

View file

@ -1,6 +1,7 @@
<script lang="ts">
import {writable} from "svelte/store"
import type {TrustedEvent} from "@welshman/util"
import {tagSpec, tagValue} from "@welshman/util"
import {publishToRelays} from "@welshman/app"
import {Comment} from "@welshman/domain"
import {isMobile, preventDefault} from "@lib/html"
@ -28,6 +29,8 @@
const {url, event, parent, onCancel, onSubmit}: Props = $props()
const h = $derived(tagValue(tagSpec("h"), event.tags))
const draftKey = new DraftKey<Values>(`comment:${event.id}:${parent?.id ?? ""}`)
const initialValues = draftKey.get()
const uploading = writable(false)
@ -54,6 +57,12 @@
.setParentFromEvent(parent ?? event)
.setProtected(await $relays.hasNip(url, 70))
// A comment on a room event is a room event too: an untagged one isn't visible to the
// group at all, so the relay neither gates it with the room nor deletes it with it.
if (h) {
eventWriter.setRoom(url, h)
}
const thunk = await command(eventWriter).then(publishToRelays([url]))
const error = await thunk.waitForError()

View file

@ -2,6 +2,7 @@
import {onMount} from "svelte"
import {writable} from "svelte/store"
import type {TrustedEvent} from "@welshman/util"
import {tagSpec, tagValue} from "@welshman/util"
import {publishToRelays} from "@welshman/app"
import {Comment} from "@welshman/domain"
import {isMobile, preventDefault} from "@lib/html"
@ -32,6 +33,7 @@
}
const {url, event, parent, onClose, onClearParent, onSubmit}: Props = $props()
const h = $derived(tagValue(tagSpec("h"), event.tags))
const draftKey = new DraftKey<Values>(`reply:${event.id}:${parent?.id || ""}`)
const initialValues = draftKey.get()
const uploading = writable(false)
@ -64,6 +66,12 @@
.setParentFromEvent(event)
.setProtected(await $relays.hasNip(url, 70))
// A comment on a room event is a room event too: an untagged one isn't visible to the
// group at all, so the relay neither gates it with the room nor deletes it with it.
if (h) {
eventWriter.setRoom(url, h)
}
const thunk = await command(eventWriter).then(publishToRelays([url]))
draftKey.clear()