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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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 pinnedmessage, 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
already pinned when joining - a pin set before you arrived is the common case.
(
components/LiveCards.tsx), which is the right place for one thing that stays putwhile the chat moves underneath.
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.
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-messageon 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
SpinWheelEventis 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.