diff --git a/src/unified-dashboard.test.ts b/src/unified-dashboard.test.ts index e1a5358..69d349f 100644 --- a/src/unified-dashboard.test.ts +++ b/src/unified-dashboard.test.ts @@ -312,6 +312,41 @@ describe('editStatusMessageWithRetry', () => { expect(ok).toBe(false); expect(slept).toBe(0); }); + + it('works with a channel method that relies on `this` when bound (regression)', async () => { + class FakeChannel { + client = { ok: true }; + edits = 0; + async editMessage(): Promise { + // Mirrors the real editMessage: throws if `this` is lost. + if (!this.client) throw new Error('this.client is undefined'); + this.edits++; + } + } + const channel = new FakeChannel(); + + // A detached method reference loses `this` → every attempt fails. + const detached = channel.editMessage; + const detachedOk = await editStatusMessageWithRetry({ + editOnce: () => detached(), + maxRetries: 0, + retryDelayMs: 0, + sleep: async () => {}, + }); + expect(detachedOk).toBe(false); + expect(channel.edits).toBe(0); + + // Binding to the channel (the fix) preserves `this` and succeeds. + const bound = channel.editMessage.bind(channel); + const boundOk = await editStatusMessageWithRetry({ + editOnce: () => bound(), + maxRetries: 0, + retryDelayMs: 0, + sleep: async () => {}, + }); + expect(boundOk).toBe(true); + expect(channel.edits).toBe(1); + }); }); describe('createCoalescingTrigger', () => { diff --git a/src/unified-dashboard.ts b/src/unified-dashboard.ts index df28687..ff92248 100644 --- a/src/unified-dashboard.ts +++ b/src/unified-dashboard.ts @@ -916,7 +916,10 @@ export async function startUnifiedDashboard( // immediately spawn a fresh status message. Retry the edit a couple of // times with a delay first; only give up (and repost) if all fail. const editId = statusMessageId; - const editMessage = channel.editMessage; + // Bind to the channel: a detached method reference loses `this` and the + // edit throws ("this.client is undefined"), which would make every + // update fail and repost a fresh (notifying) message. + const editMessage = channel.editMessage.bind(channel); // First render after start: 0 retries → repost immediately if the edit // fails. Steady state: retry twice at 15s before reposting. const maxRetries = firstStatusRender ? 0 : STATUS_EDIT_MAX_RETRIES;