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

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:
Ilia Denisov
2026-06-19 16:50:48 +02:00
parent c67a5d51f1
commit 6e77de4c1e
34 changed files with 331 additions and 86 deletions
+13 -5
View File
@@ -446,9 +446,10 @@ disguised robot stays indistinguishable from a person.
**sleeps 00:0007:00** anchored to the **opponent's** profile timezone with a
per-game drift of **±3 h** (fallback UTC), so its night overlaps the human's
rather than running anti-phase; on a daytime nudge it replies near the move's lower
band; it proactively nudges the idle human on a **lengthening, randomized schedule**the
first ~60-90 min into the turn, each later reminder spaced further out toward 1-6 h — so a long
wait gets a handful of increasingly-spaced nudges rather than an hourly stream.
band; it proactively nudges the idle human on a **sparse, randomized schedule**every nudge
waits a uniform random **9-12 h** (the first measured from the turn start, each later one from
the previous nudge), so a neglected turn gets only a handful of widely-spaced reminders. A nudge
that would fall inside the sleep window is skipped and fires at the first scan after wake.
- **Dead-endgame timing**: once the **two most recent moves are both passes**, the board and the
robot's rack are frozen and it is bound to pass again, so the robot drops the long late-game
think time and answers on a **shortened schedule scaled to the human's own last (pass) think
@@ -570,8 +571,15 @@ in either direction (the enqueue excludes the caller's `BlockedWith` set);
recipient answers by moving** (the move path calls a wired `NudgeClearer`). The mask is
inverted so "anything unread" is a plain `unread_seats <> 0`, which the per-viewer
`unread_chat` game-view flag (seeding the lobby and in-game unread **dot**), the admin
unread filter and the unread gauge all use. On each clear the publish-to-read latency is
recorded; the read time itself is not retained.
unread filter and the unread gauge all use. A second per-viewer flag, **`unread_messages`**
(`unread_seats <> 0 AND kind = 'message'`), reports whether any unread entry is a real
message rather than only a nudge, so the **dot is coloured**: the regular danger colour
when a message is unread, a softer amber when only nudges are. Both flags share the
REST-seed-then-event-bump lifecycle (the live-event game-view leaves them false; a nudge
event raises only `unread_chat`, a message event raises both). The lobby additionally
**floats games with any unread entry to the top** of the your-turn and opponent-turn
sections (the finished section keeps its activity order). On each clear the publish-to-read
latency is recorded; the read time itself is not retained.
- **Profile**: `preferred_language` (en/ru, edited in Settings), display name, email
(confirm-code binding, see §4), **timezone**, the daily **away window** and the
block toggles — all editable through `account.UpdateProfile`, which validates them: