Let a card say it is open, rather than infer it from a clock

Kick's polls run on a timer and the card worked out whether it was still
taking answers by counting down. A Matrix poll runs until somebody ends it,
so that card would have opened closed and refused every vote in it.

The service's own word wins where it gives one, the countdown stands where
it does not, and the timer bar is drawn only where there is time to show.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-04 18:02:56 -04:00
co-authored by Claude Opus 5
parent da09eed541
commit 56a0f2571e
3 changed files with 18 additions and 4 deletions
+1 -1
Submodule nobilis updated: a59b355664...4f306c2192
+5 -2
View File
@@ -53,7 +53,10 @@ function Card({ card, reviewing = false }: { card: LiveCard; reviewing?: boolean
const elapsed = Math.max(0, (now - card.receivedAt) / 1000)
const left = reviewing || card.closed ? 0 : Math.max(0, card.remaining - elapsed)
const open = left > 0
// A clock where the service runs one, and its own word where it does not:
// a Matrix poll runs until somebody ends it, so a card that worked the
// answer out from a countdown would open closed.
const open = reviewing || card.closed ? false : (card.open ?? left > 0)
const isPrediction = card.kind === 'prediction'
const total = card.options.reduce((sum, o) => sum + o.votes, 0)
const share = (votes: number): number => (total > 0 ? Math.round((votes / total) * 100) : 0)
@@ -216,7 +219,7 @@ function Card({ card, reviewing = false }: { card: LiveCard; reviewing?: boolean
{/* Time, draining right to left the way the services' own do. Left out
on something being read back: it has no time left to show. */}
{!reviewing && (
{!reviewing && card.duration > 0 && (
<div className="poll-timer" aria-hidden="true">
<span
className="poll-timer-fill"
+12 -1
View File
@@ -165,6 +165,14 @@ export interface LiveCard {
yourReturn?: number | null
/** "open", "locked", "resolved" - whatever the service calls it. */
state?: string | null
/**
* Whether it is still taking answers, where the service says so outright.
*
* Kick's polls run on a clock and this is absent; a Matrix poll runs until
* somebody ends it, so the countdown would never start and the card would
* open closed.
*/
open?: boolean
/**
* When the daemon heard this, in unix seconds.
*
@@ -1385,8 +1393,11 @@ export class ChatStore {
* Kick's broadcast catches up.
*/
votePoll(bufferId: string, optionId: string | number): void {
// Both spellings of the same answer: Kick numbers its options and Matrix
// gives them ids of their own, and which one the daemon reads follows
// from which service the conversation belongs to.
void window.moho
.rpc('votePoll', { bufferId, optionId: Number(optionId) })
.rpc('votePoll', { bufferId, optionId: Number(optionId), answerId: String(optionId) })
.catch((e: Error) => this.toast('error', e.message))
}