perf(vibe): stop building a plasma and reading the library on a phone

Two things made the Vibe page crawl on mobile.

The ambient aura pushed three counter-rotating layers through an SVG
turbulence and displacement pass whose noise field was animated in SMIL.
SVG filters rasterise on the CPU, so that regenerated the whole field
every frame, under a blur, across a 420x380 element. Below the
breakpoint the filter is no longer mounted and the layers churn behind a
plain blur instead. A phone gets the motion, not the noise pass.

The start screen read all 5,000 tracks to show fifty of them. The sample
is now drawn in the database, which keeps Surprise me uniform over the
whole library while sending only what is on screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KENqSChfyqWnor6ud2WWH6
This commit is contained in:
kami
2026-08-10 14:00:19 +04:00
parent 0a01085ed0
commit 5a019bd35c
8 changed files with 157 additions and 47 deletions
+7
View File
@@ -16,6 +16,13 @@ export default async function libraryRoutes(fastify: FastifyInstance, options: {
return tracks;
});
// Seeds for the Vibe start screen. Static path, so it is matched ahead of
// /tracks/:trackId.
fastify.get('/tracks/seeds', async (request) => {
const query = request.query as any;
return await dbService.getSeedTracks(query.limit ? parseInt(query.limit) : undefined);
});
fastify.get('/artists', async (request) => {
const query = request.query as any;
return await dbService.getArtists({
+27
View File
@@ -211,6 +211,33 @@ export class DbService {
return this.attachArtists(res.rows as Track[]);
}
/**
* A random handful of playable tracks, with the size of the pool they came
* from. The Vibe start screen offers seeds; it used to read the whole library
* to pick fifty of them, which on a phone is megabytes of JSON to draw one
* list. Sampling in the database keeps "Surprise me" uniform over everything
* playable while sending only what is shown.
*/
async getSeedTracks(limit = 50): Promise<{ total: number; tracks: Track[] }> {
const playable = `t.state NOT IN ('HIDDEN', 'DELETED')`;
const [sample, total] = await Promise.all([
this.pgClient.query(
`SELECT t.*, al.artwork_id
FROM tracks t
LEFT JOIN albums al ON al.id = t.album_id
WHERE ${playable}
ORDER BY RANDOM()
LIMIT $1`,
[Math.min(Math.max(1, limit), 200)]
),
this.pgClient.query(`SELECT COUNT(*)::int AS total FROM tracks t WHERE ${playable}`),
]);
return {
total: total.rows[0]?.total ?? 0,
tracks: await this.attachArtists(sample.rows as Track[]),
};
}
/** Attach artists array (from track_artists join) to a list of tracks. */
private async attachArtists(tracks: Track[]): Promise<Track[]> {
if (tracks.length === 0) return tracks;