Skip to content

Presses and private cards

When a person presses an action on your wisp’s card, chooses from a menu, or presses a grid cell, their app calls act. Woosh checks that the card offers that action with that value (or, for a menu, that choice), that it is not switched off, that they can see the channel, that your wisp is still there with that channel granted, and that they are within their own 3 runs and presses a second. Then your line hears act:

{
"c": 61,
"n": 111,
"t": "act",
"d": {
"server": "5c3b1a2d-8e4f-4a6b-9c7d-0e1f2a3b4c55",
"channel": "6d4c2b3e-9f5a-4b7c-8d6e-1f2a3b4c5d66",
"message": "0199dea4-1d31-7b4c-9d5e-6f7a8b9c0d21",
"id": "0199dea4-2e42-7c5d-ae6f-7a8b9c0d1e32",
"user": "7a2e4c19-0b3d-4f6a-8e25-91c3d5f0a622",
"name": "bo",
"avatar": "fox",
"action": "spin",
"expires": 1791916262000000
}
}
Key
server, channel where
message the line pressed, or a private card’s id
id the press: what you answer
user, name?, avatar? who pressed
action your action’s name
value? a menu’s choice, or the value you put on the action or cell
on? a tally that tells you: true while the person is counted, left out once they took it back
expires when your chance to answer ends, 15 seconds after the press

caio picks a game from the card’s menu:

{"c":61,"n":112,"t":"act","d":{"server":"5c3b1a2d-8e4f-4a6b-9c7d-0e1f2a3b4c55","channel":"6d4c2b3e-9f5a-4b7c-8d6e-1f2a3b4c5d66","message":"0199dea4-1d31-7b4c-9d5e-6f7a8b9c0d21","id":"0199dea4-2f13-7c6d-8e7f-8a9b0c1d2e43","user":"b84d0e37-5c2a-4e91-a6f3-0d7c8e1b9433","name":"caio","action":"game","value":"blackjack","expires":1791916270000000}}

A press Woosh refuses never reaches your wisp, and the person’s app says it did not go through. The refusals you will meet most:

Refused with Why
“That is switched off” the action is disabled on the card as it shows now
“That is not one of the menu’s choices.” a choice the menu does not offer
“The card offers no such press.” a value the card does not carry
“This card is gone” a private card Woosh no longer knows, or someone else’s
limited past their own 3 runs and presses a second: their app asks them to try again in a moment

A link is never pressed: the app opens it, and nobody hears it.

A press is answered once, before expires, with respond, as a run is (Answering covers retries and refusals). Until then the person’s app shows your wisp working on it under the card; with no answer by expires, it says your wisp did not answer. Three ways:

respond with What happens v
edit: true the pressed line itself changes, as an edit would: the game’s card moves on. Send text too, which replaces the line’s, and a card or a patch. Spends that line’s edits the pressed line
private: true an aside to the person who pressed, under the press’s id the press’s id
neither a new line in the channel, a reply to the pressed one, with text the new line

caio picked Blackjack, so the card moves on in place:

{
"i": 68,
"t": "respond",
"d": {
"to": "0199dea4-2f13-7c6d-8e7f-8a9b0c1d2e43",
"text": "Blackjack: caio draws 17.",
"card": {
"color": "#22c55e",
"blocks": [
{ "type": "text", "text": "**Blackjack**", "size": "large" },
{ "type": "text", "text": "caio draws 17." },
{
"type": "actions",
"actions": [
{ "action": "hit", "label": "Hit", "kind": "press", "tone": "accent" },
{ "action": "stand", "label": "Stand", "kind": "press" }
]
}
]
},
"edit": true
}
}
{"r":68,"v":"0199dea4-1d31-7b4c-9d5e-6f7a8b9c0d21"}

v is the line that changed. The people looking hear message.edit with the whole card and the press it answers as its run, which ends the wait in caio’s app.

A spin takes a moment. Switch the card’s actions off ("disabled": true) while it turns, and on again with the result, so nobody presses twice at once. The quickest way is a frame, here on a card whose actions row carries the key buttons; nothing is stored, and the answer that follows is the one edit:

{
"i": 67,
"t": "card.live",
"d": {
"message": "0199dea4-1d31-7b4c-9d5e-6f7a8b9c0d21",
"patch": {
"buttons": { "type": "actions", "actions": [{ "action": "spin", "label": "Spin", "kind": "press", "tone": "accent", "disabled": true }] }
}
}
}

and answer the press with the result, the actions on again (Cards has the whole spin).

The apps draw a switched-off action and do not let it be pressed. Woosh checks every press against what the card offers now: its last frame (for up to ten minutes), or else its last edit. After the frame above, a press of Spin is refused (“That is switched off”), even from an app that opened the channel later and still draws the stored card, until your answer switches it on again. A press can still slip in before the frame lands: answer it with a short private word (“Still spinning”).

card.live replaces a card in place for the people looking at the channel, between edits. It is never stored: whoever opens the channel later sees the line as its last edit left it, while its presses are checked against the frame. It is a call like any other, sent with i and answered with an ack:

{"r":67}
d
message your own line with a card
to instead of message, for a private card: the press on it you are working on
card or patch the whole card, or what changes. A private card’s frame is always a whole card

The people looking hear card.live with the line’s id, its channel, and the card or the patch you sent. A whole card replaces the one on screen; a patch is merged into it. A patch on a line with no card is refused (“The line has no card to patch.”).

Send a frame in its channel’s order, as an edit (The line), so it never lands after the edit that follows it.

A frame spends one of the line’s edits, 4 a second shared with its edits, and one of the server’s calls; a private card’s frame spends only that card’s edits. A frame past them answers limited with wait, and nobody saw it. Rather than send that one again, keep only the newest frame and send it once the wait is over (Budgets and limits). Most motion needs no frames at all: an animated picture turns a reel for free.

An aside may carry a card: a game only its player sees, a settings panel, a shop. Answer a run with private and a card:

{
"i": 70,
"t": "respond",
"d": {
"to": "0199dea4-4a10-7b2c-8d3e-4f5a6b7c8d01",
"text": "bo sat down at the slots.",
"card": {
"color": "#f97316",
"blocks": [
{ "type": "text", "text": "**Your slots**", "size": "large" },
{ "type": "grid", "rows": [[{ "emoji": "❔" }, { "emoji": "❔" }, { "emoji": "❔" }]] },
{ "type": "fields", "fields": [{ "name": "Bet", "value": "10" }, { "name": "Coins", "value": "1,000" }] },
{
"type": "actions",
"actions": [
{ "action": "spin", "label": "Spin", "kind": "press", "tone": "accent" },
{ "action": "cashout", "label": "Cash out", "kind": "press", "tone": "bad" }
]
}
]
},
"private": true
}
}
{"r":70,"v":"0199dea4-4a10-7b2c-8d3e-4f5a6b7c8d01"}

v is the private card’s id, which is the run’s own id: an aside goes under the run it answers. bo’s apps hear it as an aside:

{"t":"aside","d":{"id":"0199dea4-4a10-7b2c-8d3e-4f5a6b7c8d01","channel":"6d4c2b3e-9f5a-4b7c-8d6e-1f2a3b4c5d66","author":"d2b6f4a8-3e1c-4b7d-9a05-6c8e0f2a4b19","name":"lucky","avatar":"639201499012345678","to":"0199dea4-4a10-7b2c-8d3e-4f5a6b7c8d01","text":"bo sat down at the slots.","card":{…},"at":1791916301000000}}

A private card is kept nowhere: not stored with the channel, not in any log. Only bo’s apps that are open have it, and it is gone when he reloads. So:

  • Keep its state yourself, by its id (the v above), or in the values on its actions.
  • It has no tally: there is one person to count. A private card with a tally is refused.
  • A press on it names your wisp and the channel. bo’s app calls act with the card’s id as message, and wisp and channel beside it. Your line hears act as for any press, with message the private card’s id.
  • Woosh checks its presses as a line’s. It keeps what the card offers to press, never what it says, for an hour from when it was last sent, moved or pressed. A press it does not offer is refused as on a line, and one switched off with “That is switched off”. A press by anyone but bo, in another channel, or after that hour is refused with “This card is gone”.
  • A press on a card you no longer know (your wisp restarted and lost its state) deserves a short private answer: “This machine is gone. /spin for a new one.”
  • It moves on for its person alone. Answer a press on it with edit, and the aside is replaced in place under the same id, for bo only, kept nowhere again. Its to names the new press. Each move spends one of that card’s edits, 4 a second.
  • Frames go to its person alone, always whole. Send card.live with to, the press you are working on (one from the last minute), instead of message, and the whole card: Woosh keeps no card of his to patch. bo’s apps hear card.live with the card’s id, and its presses are checked against the frame, as on a line.
{"i":72,"t":"card.live","d":{"to":"0199dea4-4b21-7c3d-9e4f-5a6b7c8d9e02","card":{"color":"#f97316","blocks":[{"type":"text","text":"**Your slots**","size":"large"},{"type":"grid","rows":[[{"emoji":"❔"},{"emoji":"❔"},{"emoji":"❔"}]]},{"type":"fields","fields":[{"name":"Bet","value":"10"},{"name":"Coins","value":"1,000"}]},{"type":"actions","actions":[{"action":"spin","label":"Spin","kind":"press","tone":"accent","disabled":true},{"action":"cashout","label":"Cash out","kind":"press","tone":"bad","disabled":true}]}]}}}

A frame whose to is not a press on one of your private cards from the last minute is refused with “No such run or press”.

Answer a press on a private card with edit or private. A plain respond posts a line in the channel for everyone, which a private game rarely wants. To share a result, a Share button that sends one line of its own does it well.

A tally is a button Woosh counts by itself, kept as reactions are: one press a person, a second press takes it back, and the count is drawn on the card within a second, at most once a second, however many press. A giveaway with 5,000 entries in 10 seconds costs your wisp nothing.

{
"i": 12,
"t": "send",
"d": {
"channel": "6d4c2b3e-9f5a-4b7c-8d6e-1f2a3b4c5d66",
"text": "Win a monitor. Press Enter by Sunday 20:00.",
"sendId": "a5b6c7d8-e9fa-4b0c-82d3-e4f5a6b7c8d9",
"card": {
"blocks": [
{ "type": "text", "text": "**Win a monitor**", "size": "large" },
{ "type": "media", "url": "<media>/wisps/d2b6f4a8-3e1c-4b7d-9a05-6c8e0f2a4b19/7c8d9eaf-b0c1-4d2e-8f30-4a5b6c7d8e93", "alt": "A 27 inch monitor" },
{ "type": "text", "text": "Press Enter by {at:1792353600000000}.", "size": "small" },
{ "type": "actions", "actions": [{ "action": "enter", "label": "Enter", "kind": "tally", "tone": "accent" }] }
]
}
}
}
  • The count is Woosh’s. Whatever count you write is replaced with the real one the next time it is drawn, and left out while nobody is counted. It is kept with the line, so whoever opens the channel later sees it. An edit that keeps the tally’s name keeps its count.
  • A card’s tallies have names of their own, since each is counted by its name. A tally carries no value.
  • A count shows the card as stored. Each time it is drawn, the people looking get the card as your last edit left it, counted, and presses are checked against that again. Move a tally’s card on with edits rather than frames.
  • Hearing each press is a choice. Without tell, your wisp hears nothing. With "tell": true, every press and take-back is an act with on (counted) or without it (taken back). You may answer it as any press (a private “You’re in”), but you need not: the person’s app waits on no answer to a tally, since the count is the answer.
  • Read who it counts when you need them with tally, a call on the line, up to a thousand at a time in the order of their ids:
{"i":90,"t":"tally","d":{"message":"0199dea4-3064-7e7f-8081-9c0d1e2f3a54","action":"enter","limit":1000}}
{"r":90,"v":[{"user":"3f1c9a52-6d1e-4c8b-9a51-2b7e0c4d8f11","at":1791916201150000},{"user":"7a2e4c19-0b3d-4f6a-8e25-91c3d5f0a622","at":1791916203410000}]}

at is when they pressed. limit is 1 to 1,000, and 1,000 when left out. For the next page, pass the last user as after; a page shorter than limit is the last.

  • Or let Woosh draw. draw picks up to count (1 to 100) of the people a tally counts, at random, so no list need leave Woosh. Fewer come back when it counts fewer:
{"i":92,"t":"draw","d":{"message":"0199dea4-3064-7e7f-8081-9c0d1e2f3a54","action":"enter","count":1}}
{"r":92,"v":["7a2e4c19-0b3d-4f6a-8e25-91c3d5f0a622"]}

Both read only your wisp’s own lines, and each spends one of the server’s calls.

© 2026 Woosh AS. All rights reserved.