You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs-src/spectrum-ts/content.mdx.vel
+44-6Lines changed: 44 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -1,14 +1,14 @@
1
1
---
2
2
title: "Content"
3
-
description: "Build text, attachments, voice, contacts, polls, rich links, groups, and platform-specific content for outgoing messages"
3
+
description: "Build text, attachments, voice, contacts, polls, rich links, groups, replies, edits, typing indicators, and platform-specific content for outgoing messages"
4
4
---
5
5
6
6
import { TypeTooltip } from "/snippets/type-tooltip.mdx";
7
7
8
8
{% set ci = symbol("ts:spectrum-ts#ContentInput") %}
9
9
{% set cb = symbol("ts:spectrum-ts#ContentBuilder") %}
10
10
11
-
Spectrum exposes a family of content builders — `text`, `attachment`, `voice`, `contact`, `richlink`, `poll`, `group`, and `custom` — plus a string shortcut that's equivalent to `text()`. Any API that takes a <TypeTooltip name="ContentInput" type={`{{ ci.signature }}`} /> accepts a plain string or a <TypeTooltip name="ContentBuilder" type={`{{ cb.signature }}`} />.
11
+
Spectrum exposes a family of content builders — `text`, `attachment`, `voice`, `contact`, `richlink`, `poll`, `group`, `custom`, `reaction`, `reply`, `edit`, and `typing` — plus a string shortcut that's equivalent to `text()`. Any API that takes a <TypeTooltip name="ContentInput" type={`{{ ci.signature }}`} /> accepts a plain string or a <TypeTooltip name="ContentBuilder" type={`{{ cb.signature }}`} />.
12
12
13
13
## Text
14
14
@@ -200,7 +200,45 @@ import { custom } from "spectrum-ts";
The raw payload round-trips through the provider's `actions.send` — it's up to the provider to interpret it.
203
+
The raw payload round-trips through the provider's `send` action — it's up to the provider to interpret it.
204
+
205
+
## Replies
206
+
207
+
Send a threaded reply by wrapping content with the message being replied to. See [Reactions and replies](/spectrum-ts/reactions-and-replies) for the full details and sugar methods.
208
+
209
+
```ts
210
+
import { reply, text } from "spectrum-ts";
211
+
212
+
await space.send(reply(text("Got it"), message));
213
+
```
214
+
215
+
`reply()` cannot wrap `reply`, `edit`, `reaction`, `group`, or `typing` content.
216
+
217
+
## Edits
218
+
219
+
Rewrite the content of a previously-sent outbound message. Edits are fire-and-forget — `space.send(edit(...))` resolves to `undefined`.
`edit()` cannot wrap `edit`, `reply`, `reaction`, `group`, or `typing` content.
229
+
230
+
## Typing indicators
231
+
232
+
Send a typing indicator signal through the content pipeline. Defaults to `"start"`.
233
+
234
+
```ts
235
+
import { typing } from "spectrum-ts";
236
+
237
+
await space.send(typing()); // start typing
238
+
await space.send(typing("stop")); // stop typing
239
+
```
240
+
241
+
`space.startTyping()`, `space.stopTyping()`, and `space.responding(fn)` are sugar over `space.send(typing(...))`. Platforms without a typing-indicator API silently no-op.
204
242
205
243
## Composing multiple items
206
244
@@ -215,9 +253,9 @@ await space.send(
215
253
216
254
This runs one `send()` per item on the underlying provider — not a single compound message. Reach for `group(...)` instead when you specifically want them rendered as one bundled unit.
217
255
218
-
## Replies
256
+
## Replies (sugar)
219
257
220
-
`message.reply(...)` has the same variadic signature:
258
+
`message.reply(...)` has the same variadic signature and delegates to `space.send(reply(...))` internally:
221
259
222
260
```ts
223
261
await message.reply(
@@ -226,4 +264,4 @@ await message.reply(
226
264
);
227
265
```
228
266
229
-
On platforms without thread support, `reply()` resolves as a no-op. If you need guaranteed delivery, use `space.send(...)` instead.
267
+
On platforms without thread support, `reply()` resolves as a no-op. If you need guaranteed delivery, use `space.send(...)` instead. See [Reactions and replies](/spectrum-ts/reactions-and-replies) for the canonical form and more details.
| `space.params` | No | Zod schema for additional space creation parameters — surfaces as the second arg to `platform(app).space()`. |
96
99
| `lifecycle.createClient` | Yes | Creates the platform client. Receives `config`, `projectId`, `projectSecret` (both may be `undefined`), and `store`. |
97
100
| `lifecycle.destroyClient` | No | Tears down the client on shutdown. Omit if no cleanup is needed. |
| `send` | Yes | Dispatches a content item to a space. All content types — text, attachments, reactions, replies, edits, typing indicators — flow through this single action. Return a `ProviderMessageRecord` for content that produces a message, or `undefined` for fire-and-forget signals (reactions, typing, edits). |
103
+
| `actions.getMessage` | No | Fetches a message by ID from a space. Powers `space.getMessage(id)`. |
99
104
| `events.[custom]` | No | Additional async generators for platform-specific events — exposed on `app.[eventName]`. |
100
-
| `actions.send` | Yes | Sends a single content item to a space. Invoked once per item when multiple are passed. |
101
-
| `actions.startTyping` | No | Shows a typing indicator. |
102
-
| `actions.stopTyping` | No | Hides a typing indicator. |
103
-
| `actions.reactToMessage` | No | Reacts to a message. Missing → `message.react(...)` becomes a no-op. |
104
-
| `actions.replyToMessage` | No | Sends a threaded reply. Missing → `message.reply(...)` becomes a no-op. |
105
105
| `message.schema` | No | Zod schema for extra properties on incoming messages. |
106
106
| `static` | No | Constants attached to the platform object (e.g. tapback names). |
107
107
108
108
## Event producers
109
109
110
110
Every event generator receives `{ client, config, store }` and returns an `AsyncIterable`. The signature is <TypeTooltip name="EventProducer" type={`{{ ep.signature }}`} />.
111
111
112
+
The core `messages` stream lives at the top level of the definition. Optional custom event streams (presence, read receipts, etc.) live inside `events`:
Non-`messages` events are auto-wired as flat properties on both the Spectrum instance (`app.typing`) and the narrowed platform instance (`myPlatform(app).typing`).
128
+
Custom events are auto-wired as flat properties on both the Spectrum instance (`app.presence`) and the narrowed platform instance (`myPlatform(app).presence`).
Copy file name to clipboardExpand all lines: docs-src/spectrum-ts/reactions-and-replies.mdx.vel
+68-14Lines changed: 68 additions & 14 deletions
Original file line number
Diff line number
Diff line change
@@ -5,11 +5,26 @@ description: "React to incoming messages and send threaded replies"
5
5
6
6
Both `react` and `reply` live directly on an incoming message. They no-op silently on platforms that don't support the feature — no `try/catch` required.
7
7
8
+
Spectrum also exports first-class `reaction()` and `reply()` content builders that you can pass directly to `space.send(...)` — the sugar methods on `message` delegate through the same `send` pipeline.
9
+
8
10
## Reactions
9
11
10
-
```ts
11
-
await message.react("love");
12
-
```
12
+
<Tabs>
13
+
<Tab title="Sugar (message.react)">
14
+
```ts
15
+
await message.react("love");
16
+
```
17
+
</Tab>
18
+
<Tab title="Canonical (space.send)">
19
+
```ts
20
+
import { reaction } from "spectrum-ts";
21
+
22
+
await space.send(reaction("love", message));
23
+
```
24
+
</Tab>
25
+
</Tabs>
26
+
27
+
Both forms are equivalent — `message.react(emoji)` delegates to `space.send(reaction(emoji, message))` internally.
13
28
14
29
The reaction string is platform-specific. For iMessage, use the built-in tapback constants:
15
30
@@ -19,6 +34,8 @@ import { imessage } from "spectrum-ts/providers/imessage";
19
34
await message.react(imessage.tapbacks.laugh);
20
35
```
21
36
37
+
`reaction()` rejects reaction messages as targets — reacting to a reaction throws at build time.
38
+
22
39
Available tapbacks:
23
40
24
41
| Constant | Value |
@@ -32,25 +49,62 @@ Available tapbacks:
32
49
33
50
## Threaded replies
34
51
35
-
`message.reply(...)` takes the same variadic content input as `space.send(...)`:
52
+
<Tabs>
53
+
<Tab title="Sugar (message.reply)">
54
+
```ts
55
+
await message.reply("Replying to your message.");
36
56
37
-
```ts
38
-
await message.reply("Replying to your message.");
57
+
await message.reply(
58
+
"Here's the attachment you asked for:",
59
+
attachment("/path/to/file.pdf"),
60
+
);
61
+
```
62
+
</Tab>
63
+
<Tab title="Canonical (space.send)">
64
+
```ts
65
+
import { reply, text } from "spectrum-ts";
39
66
40
-
await message.reply(
41
-
"Here's the attachment you asked for:",
42
-
attachment("/path/to/file.pdf"),
43
-
);
44
-
```
67
+
await space.send(reply(text("Replying to your message."), message));
68
+
```
69
+
</Tab>
70
+
</Tabs>
71
+
72
+
Both forms are equivalent — `message.reply(content)` wraps each content item in `reply(content, message)` and delegates to `space.send(...)` internally.
45
73
46
74
On platforms with thread support (iMessage, WhatsApp Business), this sends a threaded reply. On platforms without, the call resolves as a no-op — **the reply is not downgraded to a regular send**. If you need guaranteed delivery, use `space.send(...)` instead.
47
75
76
+
`reply()` cannot wrap `reply`, `edit`, `reaction`, `group`, or `typing` content — the builder throws at construction time.
`edit()` takes new content and the outbound message to rewrite. Edits are fire-and-forget — `space.send(edit(...))` resolves to `undefined`.
98
+
99
+
`edit()` cannot wrap `edit`, `reply`, `reaction`, `group`, or `typing` content.
100
+
48
101
## When to use what
49
102
50
103
| Want to | Use |
51
104
|---|---|
52
105
| Send fresh content into the conversation | `space.send(...)` |
53
-
| Reply in-thread to a specific message | `message.reply(...)` |
54
-
| React to a specific message | `message.react(reaction)` |
106
+
| Reply in-thread to a specific message | `message.reply(...)` or `space.send(reply(...))` |
107
+
| React to a specific message | `message.react(emoji)` or `space.send(reaction(emoji, message))` |
108
+
| Rewrite a sent message | `message.edit(content)` or `space.send(edit(content, message))` |
55
109
56
-
`space.send` is the safe default — it works on every platform. Reach for `reply` when the threading behaviour is meaningful (e.g. replying in a busy group chat) and you're willing to accept the no-op on platforms that lack threads.
110
+
`space.send` is the safe default — it works on every platform. The sugar methods (`message.reply`, `message.react`, `message.edit`) and the canonical content builders (`reply()`, `reaction()`, `edit()`) are interchangeable — they both route through the same `send` pipeline.
Copy file name to clipboardExpand all lines: docs-src/spectrum-ts/spaces-and-users.mdx.vel
+2Lines changed: 2 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -59,6 +59,8 @@ await space.startTyping();
59
59
await space.stopTyping();
60
60
```
61
61
62
+
These are sugar for `space.send(typing("start"))` and `space.send(typing("stop"))` — see [Content](/spectrum-ts/content#typing-indicators) for the canonical form.
63
+
62
64
### Automatic with `responding`
63
65
64
66
`responding` is the recommended pattern. It guarantees the typing indicator is cleared even if the inner function throws:
0 commit comments