# Raw acks

Reply through discord.js's own methods on this.event and seedcord stops tracking the interaction. Covers the stale state that leaves, the lint rule reporting it, and the two cases that need one.

On gateway, `this.event` is the discord.js interaction, so you can call `this.event.reply()` and it reaches Discord the same way `this.reply()` does. It skips the ack state that seedcord's own methods keep.

## A raw ack leaves the state stale

The reply sender reads the interaction's state once, when seedcord constructs your handler. That happens before `execute()` runs, and only seedcord's own reply methods move it afterwards. If you send an ack yourself, the copy stays where the last seedcord call left it.

```ts title="src/handlers/Ban.ts"
import { SlashHandler, SlashRoute } from '@seedcord/gateway';

@SlashRoute('ban')
export class Ban extends SlashHandler<'ban'> {
    public async execute(): Promise<void> {
        await this.event.reply('Banned.');

        await this.edit('Banned, and sessions revoked.');
    }
}
```

Discord accepts the first line. The second throws.

```txt output
edit() was called when nothing has been sent yet.
Send with reply() or defer() first, then edit() rewrites it.
(route slash:ban)
```

Every later call reads that same stale state, so [`followUp()`](https://docs.seedcord.org/packages/gateway/latest/repliable-handler#follow-up) and [`delete()`](https://docs.seedcord.org/packages/gateway/latest/repliable-handler#delete) throw too.

## The lint rule

The `edit()` throw above only fires when someone runs the command. The lint rule reports the raw call while you write it.

`@seedcord/no-raw-interaction-acks` reports every ack method on a repliable interaction inside a handler or middleware class, plus `respond()` on an autocomplete one. The recommended preset turns it on as an error.

```txt output
src/handlers/Ban.ts
  6:15  error  Reply through this.reply().  @seedcord/no-raw-interaction-acks
```

{/* prettier-ignore-start */}

| discord.js      | seedcord                                     |
| --------------- | -------------------------------------------- |
| `reply()`       | `this.reply()`                               |
| `deferReply()`  | `this.defer()`                               |
| `editReply()`   | `this.edit()`                                |
| `followUp()`    | `this.followUp()`                            |
| `update()`      | `this.update()`                              |
| `deferUpdate()` | `this.deferUpdate()`                         |
| `showModal()`   | `this.showModal()`                           |
| `deleteReply()` | `this.delete()`                              |
| `fetchReply()`  | the message a reply member already returned  |
| `respond()`     | `this.respond()`, on an autocomplete handler |

{/* prettier-ignore-end */}

The rule resolves the receiver's type, which is how it reports `this.event.reply()` and a local alias alike. It misses a destructured `const { reply } = this.event`, since that call doesn't keep a receiver to resolve.

## When you actually need one

* **An embed or a poll.** The ComponentsV2 flag on every seedcord reply forbids both. `this.event.reply()` sends them.
* **A click you collected yourself.** Gateway only. `message.awaitMessageComponent()` hands you a component interaction that never reached seedcord's dispatcher, so no handler and no sender exist for it. That click is its own interaction. Answering it leaves your handler's state where it was. An http bot answers each interaction in its own request, and a collector needs a process that stays alive between them.

seedcord has a replacement for each. Text Display and Container are Discord's replacements for `content` and `embeds` under the flag. seedcord routes a click by its customId to a [`ButtonHandler`](https://docs.seedcord.org/packages/gateway/latest/button-handler), for example, which any process can answer. A collector runs in the process that opened it, so a restart drops every prompt still waiting.

For the interaction that seedcord dispatched, pass the handler's [`sender`](https://docs.seedcord.org/packages/gateway/latest/repliable-handler#sender) to your helper. It's public. When your helper replies through it, the ack state your handler reads moves too.

```ts
import type { ReplySender } from '@seedcord/gateway';

async function confirmSaved(sender: ReplySender): Promise<void> {
    await sender.followUp('Saved to the audit log.');
}

@SlashRoute('ban')
export class Ban extends SlashHandler<'ban'> {
    public async execute(): Promise<void> {
        await this.reply('Banned.');

        await confirmSaved(this.sender);
    }
}
```

`confirmSaved` takes the sender because the reply members stay protected. Calling `handler.reply()` from outside the class doesn't compile. The sender carries the same methods.

Since the rule checks code inside handler and middleware classes, answer a collected click from a plain function like this one.

```ts
import type { Message } from 'discord.js';

export async function acknowledgeClick(
    prompt: Message
): Promise<void> {
    const click = await prompt.awaitMessageComponent({
        time: 30_000
    });

    await click.reply('Got it.');
}
```

seedcord ships [`getConfirmation`](https://docs.seedcord.org/packages/gateway/latest/get-confirmation) for the common confirm-then-act case, which collects the click and answers it for you.

> **Http only**
>
> Http adds [`this.api`](https://docs.seedcord.org/packages/http/latest/repliable-handler#api), a typed REST client over `core.rest` from `@discordjs/core/http-only`. Use it for Discord calls beyond the reply surface, such as `this.api.guilds.editMember()`.
>
> The callbacks on `api.interactions` answer the interaction directly, which leaves the state stale the same way. The lint rule reads discord.js interaction types alone, so those calls pass it.
