fix(frontend): bound Call-to-Play projection work

A valid remote view could make the renderer reduce and mount thousands of calls
every second. A single long chat also re-sorted its growing message array after
every append during every reduction.

Keep only the highest-ranked 128 nominations and latest 256 messages for UI
projection. Events are sorted once before folding, so chat messages remain in
stable order without repeated growing-array sorts. Backend author history and
creator authority are unchanged.

Test Plan:
- `just frontend-test` -- passed; 94 frontend tests passed.
- `just fmt` -- TypeScript formatting completed; the recipe then hit the
  pre-existing generated security-report Markdown-lint failures.
- `git diff --cached --check` -- passed.
This commit is contained in:
ddidderr committed 2026-09-12 12:28:25 +02:00
1 parent 30663fea34
commit 078294ac83
2 files changed
+42 -2

No files matched your search

@@ -2,6 +2,8 @@ import {
CALL_TO_PLAY_CONNECTING_MESSAGE,
CHECKIN_LEAD_MS,
EXPIRED_RETENTION_MS,
MAX_VISIBLE_CALL_TO_PLAY_MESSAGES,
MAX_VISIBLE_CALL_TO_PLAY_NOMINATIONS,
TERMINAL_RETENTION_MS,
activeCallCount,
callToPlayPublishErrorMessage,
@@ -291,6 +293,40 @@ Deno.test('terminal calls sort last and do not contribute to the badge', () => {
assertEquals(statusOf(cancelled, NOW + 3), 'cancelled', 'cancelled ticker status');
});
Deno.test('large Call to Play views are bounded to the ranked UI subset', () => {
const events = Array.from({ length: MAX_VISIBLE_CALL_TO_PLAY_NOMINATIONS + 1 }, (_, index) => ({
...create(),
id: `create-${index}`,
call_id: `call-${index}`,
}));
assertEquals(
reduceCallToPlayEvents(events, NOW).length,
MAX_VISIBLE_CALL_TO_PLAY_NOMINATIONS,
'rendered nominations must stay within the UI budget',
);
});
Deno.test('one call exposes only the latest bounded chat history', () => {
const messages = Array.from({ length: MAX_VISIBLE_CALL_TO_PLAY_MESSAGES + 1 }, (_, index) =>
event(
`message-${index}`,
'Bob',
{ SendMessage: { text: `message ${index}` } },
NOW + index + 1,
)
);
const [nomination] = reduceCallToPlayEvents([create(), ...messages], NOW + messages.length + 1);
assertEquals(
nomination.messages.length,
MAX_VISIBLE_CALL_TO_PLAY_MESSAGES,
'visible messages must stay within the UI budget',
);
assertEquals(nomination.messages[0].text, 'message 1', 'oldest excess message is removed');
});
Deno.test('incoming Call to Play views replace removed author slices wholesale', () => {
const previous = { events: [create(), event('join', 'Bob', 'Rsvp', NOW + 1)] };
const incoming = { events: [create()] };