Kick: a pinned chat message is invisible here #130

Closed
opened 2026-09-06 12:04:38 -04:00 by Salastil · 1 comment
Owner

kick.com pins a message above the chat - a streamer pins the link, the rules, the
thing being talked about - and it stays there until it is replaced. moho ignores it
entirely: nothing in nobilis/src/backend/kick/ handles a pin event or reads a pinned
message, so the one line a channel wants everybody to have read is the one line moho
does not show.

Kick sends it over the same Pusher socket the rest of the chat arrives on, so this is
a new event arm beside the seventeen already handled, not a new transport.

What is needed

  • Handle the pin and unpin events on the chatroom channel, and read whatever is
    already pinned when joining - a pin set before you arrived is the common case.
  • Draw it above the log, where the live poll and prediction cards already sit
    (components/LiveCards.tsx), which is the right place for one thing that stays put
    while the chat moves underneath.
  • Unpinning it is a moderator gesture and belongs with the rest of #76's moderation,
    once the display exists.

Done looks like: the pinned message is on screen for as long as it is pinned.

Reference: kick.com's own chat.

kick.com pins a message above the chat - a streamer pins the link, the rules, the thing being talked about - and it stays there until it is replaced. moho ignores it entirely: nothing in `nobilis/src/backend/kick/` handles a pin event or reads a pinned message, so the one line a channel wants everybody to have read is the one line moho does not show. Kick sends it over the same Pusher socket the rest of the chat arrives on, so this is a new event arm beside the seventeen already handled, not a new transport. **What is needed** - Handle the pin and unpin events on the chatroom channel, and read whatever is already pinned when joining - a pin set before you arrived is the common case. - Draw it above the log, where the live poll and prediction cards already sit (`components/LiveCards.tsx`), which is the right place for one thing that stays put while the chat moves underneath. - Unpinning it is a moderator gesture and belongs with the rest of #76's moderation, once the display exists. **Done looks like:** the pinned message is on screen for as long as it is pinned. Reference: kick.com's own chat.
Salastil added the featureprotocol labels 2026-09-06 12:04:38 -04:00
Salastil removed the protocol label 2026-09-06 12:49:17 -04:00
Author
Owner

Done in nobilis 117dda1 and moho 83088eb.

Both routes in, as the ticket asked: the Pusher event for a pin made while you are watching, and /api/v2/channels/{slug}/pinned-message on opening the channel, since a pin set before you arrived is the ordinary case. It draws above the log and above the poll cards — it outlasts them, since a poll runs for a minute and a pin stays until the channel replaces it.

Dismissing it is remembered (asked for alongside the ticket). A pin stays up for hours, so a bar returning on every restart would be one somebody dismisses daily. It is a preference rather than window state, so it survives a restart and a pin put away in one window is put away in a popped-out one. Keyed on the pinned message's own id, not the channel — putting this one away must not put away the next one — and capped at 100 so the list cannot grow without end.

Two decisions worth recording.

The events are matched by shape rather than by exact name, which is deliberately the opposite of the rule the subscription events follow a few arms below. Those are matched exactly because three of them mean the same thing and had to be told apart. This is the other case: Kick documents none of it, the names come from wrappers that reverse-engineered the socket, and matching one exactly means a rename turns the feature off silently. Anchored rather than a bare search, so a SpinWheelEvent is not read as a pin.

Direction is read from the name, not from an empty payload — an unpin may echo the message being unpinned, and read as a pin that would put back the very thing it announced the removal of. A pin event carrying nothing readable is left alone rather than treated as an unpin: a shape having moved on is not a channel taking its pin down.

What is and is not verified. The fetch path was run against a live channel: the request goes out, returns 401 (the stored token has expired), is logged at debug, and 200 frames of chat arrive past it untouched — which is exactly the designed degradation. The socket event path is not live-verified, because that needs a streamer to pin something while the probe is watching. It is unit-covered against the shapes the wrappers document, and Kick's own page is behind a Cloudflare challenge so its bundle could not be read to confirm the names.

Unpinning stays with #76's moderation, as the issue says. 494 tests pass.

Done in nobilis [117dda1](https://git.salastil.com/Salastil/nobilis/commit/117dda1) and moho [83088eb](https://git.salastil.com/Salastil/moho/commit/83088eb). Both routes in, as the ticket asked: the **Pusher event** for a pin made while you are watching, and **`/api/v2/channels/{slug}/pinned-message`** on opening the channel, since a pin set before you arrived is the ordinary case. It draws above the log and above the poll cards — it outlasts them, since a poll runs for a minute and a pin stays until the channel replaces it. **Dismissing it is remembered** (asked for alongside the ticket). A pin stays up for hours, so a bar returning on every restart would be one somebody dismisses daily. It is a preference rather than window state, so it survives a restart and a pin put away in one window is put away in a popped-out one. Keyed on the pinned message's own id, not the channel — putting this one away must not put away the next one — and capped at 100 so the list cannot grow without end. **Two decisions worth recording.** The events are matched **by shape rather than by exact name**, which is deliberately the opposite of the rule the subscription events follow a few arms below. Those are matched exactly because three of them mean the same thing and had to be told apart. This is the other case: Kick documents none of it, the names come from wrappers that reverse-engineered the socket, and matching one exactly means a rename turns the feature off *silently*. Anchored rather than a bare search, so a `SpinWheelEvent` is not read as a pin. Direction is read from the **name**, not from an empty payload — an unpin may echo the message being unpinned, and read as a pin that would put back the very thing it announced the removal of. A pin event carrying nothing readable is left alone rather than treated as an unpin: a shape having moved on is not a channel taking its pin down. **What is and is not verified.** The fetch path was run against a live channel: the request goes out, returns 401 (the stored token has expired), is logged at debug, and 200 frames of chat arrive past it untouched — which is exactly the designed degradation. The **socket event path is not live-verified**, because that needs a streamer to pin something while the probe is watching. It is unit-covered against the shapes the wrappers document, and Kick's own page is behind a Cloudflare challenge so its bundle could not be read to confirm the names. Unpinning stays with #76's moderation, as the issue says. 494 tests pass.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Salastil/moho#130