feat: sparser robot nudges, typed unread badge, lobby unread bump
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 53s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m26s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 13s
CI / ui (pull_request) Successful in 53s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m26s
Three owner-requested polish changes: - robot: replace the lengthening 60-90 min -> 6 h proactive-nudge ramp with a flat uniform 9-12 h wait before every nudge; the existing sleep-window gate still skips and defers a nudge that would land in the robot's night. - ui: colour the lobby/in-game unread dot by type -- the regular danger colour when a chat message is unread, a softer amber (--warn) when only nudges are. Adds a per-viewer unread_messages flag (chat_messages.kind='message') across the backend DTO, FlatBuffers wire, gateway transcode and the UI store. - ui: float games with any unread notification to the top of the lobby's your-turn and opponent-turn sections (finished keeps its order), reusing the existing unread_chat flag. Docs (ARCHITECTURE 7, FUNCTIONAL + _ru) updated. No DB migration; the new wire field is backward-compatible.
This commit is contained in:
@@ -63,6 +63,10 @@ export const app = $state<{
|
||||
* game, for the lobby and in-game unread dot. Seeded from the authoritative REST views and
|
||||
* raised by live chat/nudge events; cleared (with a backend ack) on opening the history/chat. */
|
||||
chatUnread: Record<string, boolean>;
|
||||
/** Per-game flag: at least one unread entry is a real message (not just a nudge), so the dot
|
||||
* can be coloured apart from the nudge-only case. Same lifecycle as chatUnread; nudge events
|
||||
* raise only chatUnread, message events raise both. */
|
||||
messageUnread: Record<string, boolean>;
|
||||
/** Whether an operator feedback reply awaits the player, for the lobby ⚙️ badge (combined
|
||||
* with friend requests) and the Settings → Info badge. */
|
||||
feedbackReplyUnread: boolean;
|
||||
@@ -93,6 +97,7 @@ export const app = $state<{
|
||||
localeLocked: false,
|
||||
notifications: 0,
|
||||
chatUnread: {},
|
||||
messageUnread: {},
|
||||
feedbackReplyUnread: false,
|
||||
staleInvite: false,
|
||||
welcomeRedeem: false,
|
||||
@@ -170,14 +175,18 @@ export function dismissWelcomeRedeem(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* seedChatUnread sets a game's unread flag from an authoritative per-viewer REST view (the lobby
|
||||
* list, a game's state, or a move result). The live-event GameView omits the flag, so the live
|
||||
* stream raises unread (bumpChatUnread) rather than seeding it from a GameView.
|
||||
* seedChatUnread sets a game's unread flags from an authoritative per-viewer REST view (the lobby
|
||||
* list, a game's state, or a move result): unread is any unread entry, message whether one of them
|
||||
* is a real message (the badge colour). The live-event GameView omits both, so the live stream
|
||||
* raises them on receive rather than seeding from a GameView.
|
||||
*/
|
||||
export function seedChatUnread(gameId: string, unread: boolean): void {
|
||||
export function seedChatUnread(gameId: string, unread: boolean, message: boolean): void {
|
||||
if ((app.chatUnread[gameId] ?? false) !== unread) {
|
||||
app.chatUnread = { ...app.chatUnread, [gameId]: unread };
|
||||
}
|
||||
if ((app.messageUnread[gameId] ?? false) !== message) {
|
||||
app.messageUnread = { ...app.messageUnread, [gameId]: message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,6 +201,7 @@ export function seedChatUnread(gameId: string, unread: boolean): void {
|
||||
export function markChatRead(gameId: string): void {
|
||||
if (!app.chatUnread[gameId]) return;
|
||||
app.chatUnread = { ...app.chatUnread, [gameId]: false };
|
||||
if (app.messageUnread[gameId]) app.messageUnread = { ...app.messageUnread, [gameId]: false };
|
||||
void gateway.markChatRead(gameId).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -262,6 +272,11 @@ function openStream(): void {
|
||||
router.route.params.id === e.message.gameId;
|
||||
if (!inComms) {
|
||||
app.chatUnread = { ...app.chatUnread, [e.message.gameId]: true };
|
||||
// Only a real message colours the badge; a nudge delivered as a chat_message must not
|
||||
// raise the message flag (the separate nudge event below also leaves it untouched).
|
||||
if (e.message.kind !== 'nudge') {
|
||||
app.messageUnread = { ...app.messageUnread, [e.message.gameId]: true };
|
||||
}
|
||||
showToast(e.message.kind === 'nudge' ? t('chat.nudge') : e.message.body, 'info');
|
||||
}
|
||||
} else if (e.kind === 'nudge') {
|
||||
|
||||
Reference in New Issue
Block a user