From f5061f014b24dbbe48d003d7bc00c370b5fe247a Mon Sep 17 00:00:00 2001
From: Alexa <53471801+Alexa-RR@users.noreply.github.com>
Date: Wed, 27 May 2026 10:43:31 +0300
Subject: [PATCH] play: drop playlists from Hot; raise take 12 -> 100
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
/roomserver/roomsandplaylists/hot and the path-style sibling
/roomserver/hot_roomsandplaylists/{tags} now return rooms-only — the
watch's Playlists tab fetches via /api/curatedroomplaylists +
/roomserver/playlists/{id} independently (verified
FPNPBJBCMKB.txt:053, EJDCNGBEICB.txt:1659/2192), so dropping playlists
from the Hot endpoint just stops mixing them into the rooms tab.
Wire shape unchanged: HJKAOMOICJG reads TotalResults + Results
(HJKAOMOICJG.txt:039/045) and the MKAMHOIHOJK factory discriminates
on the roomId key (MKAMHOIHOJK.txt:014), accepting a Results list
that's 100% room entries.
Take cap raised from 12 to 100 — the old limit only surfaced the
dozen highest-HotScore rooms, hiding the rest of the public catalog.
---
.../API/Rooms/V2/RoomsController.cs | 64 ++++++++++++-------
1 file changed, 40 insertions(+), 24 deletions(-)
diff --git a/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs b/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs
index b91c103..7dd266f 100644
--- a/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs
+++ b/DorkNet.Server/Controllers/API/Rooms/V2/RoomsController.cs
@@ -1731,16 +1731,25 @@ public class RoomsController(
/// roomId key per the MKAMHOIHOJK factory at
/// MKAMHOIHOJK.txt:621 — see
/// + .
+ ///
+ /// Why rooms-only despite the "and playlists" name: the
+ /// Hot tab of the watch's Play page mounts this endpoint and used to
+ /// interleave playlist tiles next to room tiles, but design intent is
+ /// that playlists appear only under the Moods/Playlists section (their
+ /// own endpoints — /api/curatedroomplaylists +
+ /// /roomserver/playlists/{id}). The watch's union deserializer
+ /// happily accepts a Results list that's 100% rooms, so we keep the
+ /// wrapper shape but drop the playlist half.
///
[HttpGet("roomserver/roomsandplaylists/hot")]
[HttpGet("roomsandplaylists/hot")]
public async Task RoomsAndPlaylistsHot([FromQuery] string? tag)
{
- var (merged, total) = await BuildHotUnionAsync(tag);
+ var (entries, total) = await BuildHotRoomsAsync(tag);
return Ok(new Dictionary(StringComparer.Ordinal)
{
["TotalResults"] = (long)total,
- ["Results"] = merged,
+ ["Results"] = entries,
});
}
@@ -1760,12 +1769,16 @@ public class RoomsController(
// Watch joins multiple tags with "," — use the first one as a
// substring filter since RoomService.HotAsync takes a single
// tag arg. Empty / null = unfiltered.
+ //
+ // Returns rooms-only for the same reason as the wrapped
+ // RoomsAndPlaylistsHot endpoint above: playlists belong in
+ // Moods, not in the Hot tab. See that endpoint's doc-comment.
var firstTag = string.IsNullOrWhiteSpace(tagsJoined)
? null
: tagsJoined.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.FirstOrDefault();
- var (merged, _) = await BuildHotUnionAsync(firstTag);
- return Ok(merged);
+ var (entries, _) = await BuildHotRoomsAsync(firstTag);
+ return Ok(entries);
}
/// GET hot_rooms/{tagsJoined?} — rooms-only variant
@@ -1795,34 +1808,37 @@ public class RoomsController(
}
///
- /// Shared body for the two hot variants — pulls hot rooms +
- /// playlists, fans out sub-room rows in one query to avoid N+1,
- /// and interleaves into the union shape the watch's factory
- /// expects. Returns (entries, totalCount) so the wrapped variant
- /// can fill in TotalResults.
+ /// Shared body for the two hot variants — pulls hot rooms only,
+ /// fans out sub-room rows in one query to avoid N+1, and returns
+ /// the union-shape entries the watch's factory expects (each entry
+ /// happens to be a room dict; the factory accepts that). Returns
+ /// (entries, totalCount) so the wrapped variant can fill in
+ /// TotalResults.
+ ///
+ /// Take limit raised to 100 (was 12) — the Hot tab used to
+ /// surface only the top dozen rooms which is far less than the
+ /// catalog the watch's browse UI can actually render. The watch
+ /// paginates client-side if it needs to chunk further.
///
- private async Task<(List> Entries, int Total)> BuildHotUnionAsync(string? tag)
+ private async Task<(List> Entries, int Total)> BuildHotRoomsAsync(string? tag)
{
- // Sequential — both services share the scoped DbContext, which
- // is not thread-safe (Task.WhenAll here throws "A second
- // operation was started on this context instance before a
- // previous operation completed").
- var roomList = await rooms.HotAsync(tag, take: 12);
- var playlistList = await playlists.HotAsync(tag, take: 12);
+ var roomList = await rooms.HotAsync(tag, take: 100);
var roomIds = roomList.Select(r => r.Id).ToList();
- var sceneRows = await db.RoomScenes
- .Where(s => roomIds.Contains(s.RoomId))
- .OrderBy(s => s.OrderIndex)
- .ToListAsync();
+ var sceneRows = roomIds.Count == 0
+ ? new List()
+ : await db.RoomScenes
+ .Where(s => roomIds.Contains(s.RoomId))
+ .OrderBy(s => s.OrderIndex)
+ .ToListAsync();
var scenesByRoom = sceneRows
.GroupBy(s => s.RoomId)
.ToDictionary(g => g.Key, g => (IReadOnlyList)g.ToList());
- var merged = InterleaveUnion(
- roomList.Select(r => BuildRoomUnionEntry(r, scenesByRoom.GetValueOrDefault(r.Id))),
- playlistList.Select(BuildPlaylistUnionEntry));
- return (merged, roomList.Count + playlistList.Count);
+ var entries = roomList
+ .Select(r => BuildRoomUnionEntry(r, scenesByRoom.GetValueOrDefault(r.Id)))
+ .ToList();
+ return (entries, entries.Count);
}
///