| 1 | diff --git a/docs/rooms.md b/docs/rooms.md |
| 2 | index 7f1f87f..a074b77 100644 |
| 3 | --- a/docs/rooms.md |
| 4 | +++ b/docs/rooms.md |
| 5 | @@ -57,7 +57,7 @@ Room events reach device notifications and relay push callbacks the same way as |
| 6 | |
| 7 | ## In the web UI |
| 8 | |
| 9 | -**Rooms** in the relay navigation lists the rooms you can see with their access rule, member count and last message. Signed-in members create a room at the bottom of the list: the name becomes the room id, lowercased with punctuation replaced by hyphens, and the room opens once the relay accepts it. |
| 10 | +**Rooms** in the relay navigation lists the rooms you can see with their access rule, member count and last message. Signed-in members create a room at the bottom of the list: the name becomes the room id, lowercased with punctuation replaced by hyphens, unless the **Id** field names one, and the room opens once the relay accepts it. Give the id yourself when a client expects a particular shape, such as a Buzz gateway that wants a UUID. |
| 11 | |
| 12 | Open a room to read its messages, oldest first. Each message shows the author's key, their room role and the time; messages from agents carry an agent marker. Links open in place and `nostr:` links resolve through the relay. A thread shows how many replies it has and opens on its own page, where replies read in order. Reactions appear under the message they answer. Choose **load earlier** for older messages. |
| 13 | |
| 14 | diff --git a/internal/webui/components.js b/internal/webui/components.js |
| 15 | index 14e43dc..b2191c9 100644 |
| 16 | --- a/internal/webui/components.js |
| 17 | +++ b/internal/webui/components.js |
| 18 | @@ -754,8 +754,13 @@ |
| 19 | // RoomCreate signs a NIP-29 create (kind 9007) with an id derived from the |
| 20 | // name, then opens the new room. |
| 21 | class RoomCreate extends FormElement { |
| 22 | - event({name = "", about = "", access = "open"} = {}) { |
| 23 | - const title = String(name).trim(), id = roomID(title); |
| 24 | + // event builds the kind 9007 room creation. The id comes from the name |
| 25 | + // unless one is given, so a client that wants a fixed id, such as a Buzz |
| 26 | + // gateway that expects a UUID, can ask for it. |
| 27 | + event({name = "", id = "", about = "", access = "open"} = {}) { |
| 28 | + const title = String(name).trim(), given = String(id).trim().toLowerCase(); |
| 29 | + if (given && !/^[a-z0-9_-]{1,64}$/.test(given)) throw Error("Room ids use 1 to 64 lowercase letters, digits, hyphens or underscores."); |
| 30 | + id = given || roomID(title); |
| 31 | if (!id) throw Error("Enter a name with letters or digits."); |
| 32 | const tags = [["h", id], ["name", title]]; |
| 33 | if (String(about).trim()) tags.push(["about", String(about).trim()]); |
| 34 | @@ -765,7 +770,7 @@ |
| 35 | |
| 36 | async submit(form) { |
| 37 | const value = name => form.elements[name]?.value || ""; |
| 38 | - const {id, event} = this.event({name: value("name"), about: value("about"), access: value("access")}); |
| 39 | + const {id, event} = this.event({name: value("name"), id: value("id"), about: value("about"), access: value("access")}); |
| 40 | this.report("Signing…"); |
| 41 | await signAndPublish(event); |
| 42 | this.report("Created #" + id + "."); |
| 43 | diff --git a/internal/webui/rooms.html b/internal/webui/rooms.html |
| 44 | index 3a0c41e..cd63f5e 100644 |
| 45 | --- a/internal/webui/rooms.html |
| 46 | +++ b/internal/webui/rooms.html |
| 47 | @@ -11,7 +11,7 @@ |
| 48 | {{else}}<p>No rooms yet.</p> |
| 49 | {{end}}{{with (asMap .Event).next_cursor}}<p id="pages"><a href="{{browsePageURL "/rooms" $.Query "cursor" .}}">next page</a></p> |
| 50 | {{end}}{{if .Actor}}<h2 id="new-room">New room</h2> |
| 51 | -<room-create><label>Name <input name="name" required maxlength="200" placeholder="build"></label><label>Access <select name="access"><option value="open">open</option><option value="members">members only</option></select></label><label>About <textarea name="about" maxlength="2000"></textarea></label><button>Create room</button></room-create> |
| 52 | +<room-create><label>Name <input name="name" required maxlength="200" placeholder="build"></label><label>Id <input name="id" maxlength="64" pattern="[a-z0-9_-]{1,64}" placeholder="from the name"></label><label>Access <select name="access"><option value="open">open</option><option value="members">members only</option></select></label><label>About <textarea name="about" maxlength="2000"></textarea></label><button>Create room</button></room-create> |
| 53 | <noscript><p>Creating a room needs JavaScript and a connected signer.</p></noscript> |
| 54 | {{else}}<p><a href="/signin">Sign in</a> to create a room.</p> |
| 55 | {{end}}</section> |
| 56 | diff --git a/scripts/rooms.test.mjs b/scripts/rooms.test.mjs |
| 57 | index 86eccb3..67ae444 100644 |
| 58 | --- a/scripts/rooms.test.mjs |
| 59 | +++ b/scripts/rooms.test.mjs |
| 60 | @@ -142,6 +142,10 @@ test("room-create derives the id from the name and signs 9007 before opening the |
| 61 | same(event.tags, [["h", "build-release"], ["name", "Build & Release"], ["about", "nightly"], ["visibility", "members"]]); |
| 62 | same(create.event({name: "general"}).event.tags, [["h", "general"], ["name", "general"], ["visibility", "open"]]); |
| 63 | assert.throws(() => create.event({name: "!!!"}), /letters or digits/); |
| 64 | + const fixed = create.event({name: "Hermes", id: " 9782AFBD-88c9-43b1-8b3a-5a68e99d3c52 "}); |
| 65 | + assert.equal(fixed.id, "9782afbd-88c9-43b1-8b3a-5a68e99d3c52"); |
| 66 | + same(fixed.event.tags, [["h", "9782afbd-88c9-43b1-8b3a-5a68e99d3c52"], ["name", "Hermes"], ["visibility", "open"]]); |
| 67 | + assert.throws(() => create.event({name: "Hermes", id: "not valid!"}), /lowercase letters/); |
| 68 | await create.submit(s.form({name: "Build", about: "", access: "open"})); |
| 69 | assert.equal(s.sent[0].event.kind, 9007); |
| 70 | assert.equal(verifyEvent(s.sent[0].event), true); |