feat(threads-forum): channel.delete and thread/forum span guildId (v0.8.421)

deleteChannel with gossip; thread.create, thread.archive, and forum.post
spans include guildId, span.fail, and structured error logs.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-24 01:04:42 -04:00
co-authored by Cursor
parent 21cac82026
commit 98134b9abc
2 changed files with 62 additions and 6 deletions
+2
View File
@@ -35,6 +35,8 @@ Expose a single `EventEmitter` API the UI drives over IPC (`apps/pearcord/index.
| `onboarded`, `identity`, `storagePath`, `db`, `dbPath` | Session state | | `onboarded`, `identity`, `storagePath`, `db`, `dbPath` | Session state |
| `listGuilds()`, `loadGuild(guildId)`, `createGuild({ name, publicListing? })`, `joinInvite(code)` | Guild session | | `listGuilds()`, `loadGuild(guildId)`, `createGuild({ name, publicListing? })`, `joinInvite(code)` | Guild session |
**v0.8.421 (Phase 458):** `channel.delete` span + gossip; `thread.create`, `thread.archive`, `forum.post` spans include `guildId` + `span.fail`. Bundle: `npm run test:phase458-threads-forum`. See [MESSAGE_THREADS.md](../../docs/MESSAGE_THREADS.md), [FORUM_CHANNELS.md](../../docs/FORUM_CHANNELS.md).
**v0.8.420 (Phase 457):** `channel.create`, `channel.update` (rename), and `category.create` spans + error logs with `guildId`. `create-channel` IPC selects new channel and emits composer focus. Bundle: `npm run test:phase457-channels`. See [CHANNELS.md](../../docs/CHANNELS.md). **v0.8.420 (Phase 457):** `channel.create`, `channel.update` (rename), and `category.create` spans + error logs with `guildId`. `create-channel` IPC selects new channel and emits composer focus. Bundle: `npm run test:phase457-channels`. See [CHANNELS.md](../../docs/CHANNELS.md).
**v0.8.419 (Phase 456):** `session.startup` span ends with `guildCount` + `guildId`; `session.startup error` log. `identity.register` span + `identity.register error`. `guild.open` span end includes `guildId`. `loadGuild` / `createGuild` bust search cache and clear `_lastSearchFromCache`. Bundle: `npm run test:phase456-onboarding-session`. See [SESSION.md](../../docs/SESSION.md). **v0.8.419 (Phase 456):** `session.startup` span ends with `guildCount` + `guildId`; `session.startup error` log. `identity.register` span + `identity.register error`. `guild.open` span end includes `guildId`. `loadGuild` / `createGuild` bust search cache and clear `_lastSearchFromCache`. Bundle: `npm run test:phase456-onboarding-session`. See [SESSION.md](../../docs/SESSION.md).
+60 -6
View File
@@ -3077,6 +3077,9 @@ class PearcordPlatform extends EventEmitter {
this._fanoutBotEvent(BOT_EVENTS.CHANNEL_CREATE, { channel: ch }) this._fanoutBotEvent(BOT_EVENTS.CHANNEL_CREATE, { channel: ch })
this.emit('channel', ch) this.emit('channel', ch)
}) })
guildInstance.on('channel-delete', (p) => {
this.emit('channel-delete', p)
})
guildInstance.on('pin', (p) => this.emit('pin', p)) guildInstance.on('pin', (p) => this.emit('pin', p))
guildInstance.on('ban', (p) => this._onBanGossip(p)) guildInstance.on('ban', (p) => this._onBanGossip(p))
guildInstance.on('channel-settings', (p) => { guildInstance.on('channel-settings', (p) => {
@@ -10657,7 +10660,9 @@ class PearcordPlatform extends EventEmitter {
} }
async createForumPost ({ title, content, tags }) { async createForumPost ({ title, content, tags }) {
const guildId = this.guild?.guild?.id || null
const span = this.log.time('forum.post', { const span = this.log.time('forum.post', {
guildId,
title: String(title || '').trim().slice(0, 64) || null title: String(title || '').trim().slice(0, 64) || null
}) })
try { try {
@@ -10682,7 +10687,6 @@ class PearcordPlatform extends EventEmitter {
const message = await this.sendMessage(body) const message = await this.sendMessage(body)
const merged = await this.guild.setThreadRootMessage(thread.id, message.id) const merged = await this.guild.setThreadRootMessage(thread.id, message.id)
this.guild.gossipChannelUpdate(merged) this.guild.gossipChannelUpdate(merged)
const guildId = this.guild.guild.id
const palette = this.forum.parsePaletteFromTopic(forum.topic) const palette = this.forum.parsePaletteFromTopic(forum.topic)
const postTags = this.forum.normalizePostTags( const postTags = this.forum.normalizePostTags(
(tags || []).filter((t) => !palette.length || palette.includes(t)) (tags || []).filter((t) => !palette.length || palette.includes(t))
@@ -10701,19 +10705,25 @@ class PearcordPlatform extends EventEmitter {
targetId: thread.id targetId: thread.id
}) })
this.emit('channel', merged) this.emit('channel', merged)
span.end({ threadId: thread.id, tagCount: postTags.length }) span.end({ guildId, threadId: thread.id, tagCount: postTags.length })
return { thread: { ...merged, tags: postTags }, message } return { thread: { ...merged, tags: postTags }, message }
} catch (err) { } catch (err) {
this.log.error('forum.post error', { this.log.error('forum.post error', {
guildId,
title: String(title || '').trim().slice(0, 64) || null, title: String(title || '').trim().slice(0, 64) || null,
err: err?.message || String(err) err: err?.message || String(err)
}) })
span.fail(err)
throw err throw err
} }
} }
async createThread ({ messageId, name }) { async createThread ({ messageId, name }) {
const span = this.log.time('thread.create', { messageId: messageId || null }) const guildId = this.guild?.guild?.id || null
const span = this.log.time('thread.create', {
guildId,
messageId: messageId || null
})
try { try {
if (!this.guild?.guild || !this.activeChannelId) throw new Error('no active text channel') if (!this.guild?.guild || !this.activeChannelId) throw new Error('no active text channel')
const parent = await this.db.get(COLLECTIONS.CHANNELS, { const parent = await this.db.get(COLLECTIONS.CHANNELS, {
@@ -10741,13 +10751,15 @@ class PearcordPlatform extends EventEmitter {
}) })
this.selectChannel(thread.id) this.selectChannel(thread.id)
this.emit('channel', thread) this.emit('channel', thread)
span.end({ threadId: thread.id, parentId: parent.id }) span.end({ guildId, threadId: thread.id, parentId: parent.id })
return thread return thread
} catch (err) { } catch (err) {
this.log.error('thread.create error', { this.log.error('thread.create error', {
guildId,
messageId: messageId || null, messageId: messageId || null,
err: err?.message || String(err) err: err?.message || String(err)
}) })
span.fail(err)
throw err throw err
} }
} }
@@ -10841,7 +10853,12 @@ class PearcordPlatform extends EventEmitter {
} }
async archiveThread (threadId, archived = true) { async archiveThread (threadId, archived = true) {
const span = this.log.time('thread.archive', { threadId, archived: !!archived }) const guildId = this.guild?.guild?.id || null
const span = this.log.time('thread.archive', {
guildId,
threadId,
archived: !!archived
})
try { try {
if (!this.guild?.guild) throw new Error('no guild') if (!this.guild?.guild) throw new Error('no guild')
const channels = await this.guild.listChannels() const channels = await this.guild.listChannels()
@@ -10854,14 +10871,51 @@ class PearcordPlatform extends EventEmitter {
targetId: threadId targetId: threadId
}) })
this.emit('channel-update', merged) this.emit('channel-update', merged)
span.end({ threadId, archived: !!archived }) span.end({ guildId, threadId, archived: !!archived })
return merged return merged
} catch (err) { } catch (err) {
this.log.error('thread.archive error', { this.log.error('thread.archive error', {
guildId,
threadId, threadId,
archived: !!archived, archived: !!archived,
err: err?.message || String(err) err: err?.message || String(err)
}) })
span.fail(err)
throw err
}
}
async deleteChannel (channelId) {
const guildId = this.guild?.guild?.id || null
const span = this.log.time('channel.delete', { guildId, channelId: channelId || null })
try {
if (!this.guild) throw new Error('no guild')
if (!(await this._hasPerm(PERMISSION.MANAGE_CHANNELS))) {
throw new Error('no permission to delete channels')
}
const deletedId = String(channelId || '')
const wasActive = this.activeChannelId === deletedId
const payload = await this.guild.deleteChannel(deletedId)
this.guild.gossipChannelDelete(payload)
this._invalidateSearchCache()
if (wasActive) {
const channels = await this.guild.listChannels()
await this._selectGuildChannelWithUnread(channels)
}
await this._audit('channel.delete', {
guildId: this.guild.guild.id,
targetId: deletedId
})
this.emit('channel-delete', payload)
span.end({ guildId, channelId: deletedId })
return payload
} catch (err) {
this.log.error('channel.delete error', {
guildId,
channelId: channelId || null,
err: err?.message || String(err)
})
span.fail(err)
throw err throw err
} }
} }