From 1e4f2942d1377bed98955caa4d28b637bee2fdec Mon Sep 17 00:00:00 2001 From: Ewan Croft Date: Mon, 10 Aug 2026 05:56:02 +0100 Subject: [PATCH] chore(tourmaline): apply prettier formatting to fix lint failures Reformat the 9 files that failed prettier --check (analysis modules, oauth, enrich, post, config files). No behavioral changes. --- packages/tourmaline/AGENTS.md | 4 ++ .../tourmaline/src/lib/analysis/aggregator.ts | 39 ++++++++++++------- .../tourmaline/src/lib/analysis/genres.ts | 8 ++-- .../src/lib/analysis/story-recap.ts | 2 +- packages/tourmaline/src/lib/atproto/oauth.ts | 3 +- packages/tourmaline/src/lib/server/enrich.ts | 37 ++++++++++++++---- packages/tourmaline/src/lib/share/post.ts | 12 +++--- packages/tourmaline/svelte.config.js | 2 +- packages/tourmaline/vercel.json | 6 +-- 9 files changed, 77 insertions(+), 36 deletions(-) diff --git a/packages/tourmaline/AGENTS.md b/packages/tourmaline/AGENTS.md index 22ebf95..203cea1 100644 --- a/packages/tourmaline/AGENTS.md +++ b/packages/tourmaline/AGENTS.md @@ -1,15 +1,18 @@ # AGENTS.md ## Purpose + The `tourmaline` package is the core statistical engine and web frontend for the Last.fm stats migration project. It aggregates scrobble data from ATProto PDS (teal.fm) and computes detailed listening insights. ## Architectural Concepts + - **Functional Analysis Engine**: Uses utility modules in `src/lib/analysis/` to process raw scrobble data into meaningful stats (milestones, streaks, genres, etc.). - **Chart.js Abstraction**: `src/lib/components/BaseChart.svelte` serves as the primary wrapper for consistent visualization across the dashboard. - **Sequential Enrichment**: Employs a strict fetch-compute-enrich cycle to maintain data integrity and avoid UI race conditions. - **Svelte 5 / Tailwind**: Uses Svelte 5 runes for reactive state management and Tailwind CSS for styling. ## Core Files + - `src/lib/analysis/statsBuilder.ts`: Handles aggregation and statistical calculations. - `src/lib/server/enrich.ts`: Logic for API enrichment via MusicBrainz/Last.fm. - `src/lib/components/BaseChart.svelte`: Reusable Chart.js component. @@ -17,6 +20,7 @@ The `tourmaline` package is the core statistical engine and web frontend for the - `src/routes/api/enrich/[did]/+server.ts`: Enrichment batch processing endpoint. ## Instructions for Agents + - When modifying analysis logic, update tests to ensure statistical accuracy. - Enrichment follows a sequential batch approach to stay under Vercel execution limits; maintain this constraint when modifying enrichment logic. - All new visualizations should utilize `BaseChart.svelte` where possible to keep styles consistent. diff --git a/packages/tourmaline/src/lib/analysis/aggregator.ts b/packages/tourmaline/src/lib/analysis/aggregator.ts index 899b97e..82361b9 100644 --- a/packages/tourmaline/src/lib/analysis/aggregator.ts +++ b/packages/tourmaline/src/lib/analysis/aggregator.ts @@ -318,7 +318,8 @@ export class Aggregator { Math.floor((today.getTime() - firstDate.getTime()) / 86_400_000), ); const daysScrobbled = this.dailyCounts.size; - const daysScrobbledPercentage = (daysScrobbled / daysSinceFirstScrobble) * 100; + const daysScrobbledPercentage = + (daysScrobbled / daysSinceFirstScrobble) * 100; const oneHitWondersCount = [...this.artistCounts.values()].filter( (c) => c === 1, @@ -336,8 +337,9 @@ export class Aggregator { } const sortedYears = [...yearCounts.entries()].sort((a, b) => b[1] - a[1]); const mostPopularYear = sortedYears[0] ?? { year: "N/A", count: 0 }; - const mostPopularMonth = [...this.monthlyCounts.entries()] - .sort((a, b) => b[1] - a[1])[0] ?? { month: "N/A", count: 0 }; + const mostPopularMonth = [...this.monthlyCounts.entries()].sort( + (a, b) => b[1] - a[1], + )[0] ?? { month: "N/A", count: 0 }; const scrobbleStreaks = calcScrobbleStreaks(this.dailyCounts); const longestScrobbleStreak = scrobbleStreaks[0] ?? null; @@ -398,22 +400,28 @@ export class Aggregator { daysScrobbledPercentage, firstScrobble: this.firstScrobble ? { - date: new Date(this.firstScrobble.playedTime).toLocaleDateString(undefined, { - month: "short", - day: "numeric", - year: "numeric", - }), + date: new Date(this.firstScrobble.playedTime).toLocaleDateString( + undefined, + { + month: "short", + day: "numeric", + year: "numeric", + }, + ), track: this.firstScrobble.trackName, artist: this.firstScrobble.artists[0]?.name ?? "Unknown", } : null, lastScrobble: this.lastScrobble ? { - date: new Date(this.lastScrobble.playedTime).toLocaleDateString(undefined, { - month: "short", - day: "numeric", - year: "numeric", - }), + date: new Date(this.lastScrobble.playedTime).toLocaleDateString( + undefined, + { + month: "short", + day: "numeric", + year: "numeric", + }, + ), track: this.lastScrobble.trackName, artist: this.lastScrobble.artists[0]?.name ?? "Unknown", } @@ -421,7 +429,10 @@ export class Aggregator { oneHitWondersCount, oneHitWondersPercentage, mostPopularYear: { year: mostPopularYear[0], count: mostPopularYear[1] }, - mostPopularMonth: { month: mostPopularMonth[0], count: mostPopularMonth[1] }, + mostPopularMonth: { + month: mostPopularMonth[0], + count: mostPopularMonth[1], + }, scrobbleStreaks, longestScrobbleStreak, longestArtistStreak, diff --git a/packages/tourmaline/src/lib/analysis/genres.ts b/packages/tourmaline/src/lib/analysis/genres.ts index 757e409..ed82134 100644 --- a/packages/tourmaline/src/lib/analysis/genres.ts +++ b/packages/tourmaline/src/lib/analysis/genres.ts @@ -148,13 +148,15 @@ export function buildGenreProfile( artistInfos: Map, ): GenreEntry[] { const genreWeights = new Map(); - console.log(`[tourmaline] building genre profile, artistCounts: ${data.artistPlayCounts.size}, artistInfos: ${artistInfos.size}`); + console.log( + `[tourmaline] building genre profile, artistCounts: ${data.artistPlayCounts.size}, artistInfos: ${artistInfos.size}`, + ); for (const [name, count] of data.artistPlayCounts) { const info = artistInfos.get(name); if (!info) { - console.log(`[tourmaline] artist not in info: ${name}`); - continue; + console.log(`[tourmaline] artist not in info: ${name}`); + continue; } const allRaw = [...info.genres, ...info.tags]; diff --git a/packages/tourmaline/src/lib/analysis/story-recap.ts b/packages/tourmaline/src/lib/analysis/story-recap.ts index 90ea98f..ebd01de 100644 --- a/packages/tourmaline/src/lib/analysis/story-recap.ts +++ b/packages/tourmaline/src/lib/analysis/story-recap.ts @@ -35,7 +35,7 @@ export function buildStoryRecap( phases: ListeningPhase[], range: string, ): StoryRecap { - const label = range === 'all' ? 'all-time' : range.replace('d', '-day'); + const label = range === "all" ? "all-time" : range.replace("d", "-day"); const cards: StoryRecapCard[] = []; // Intro diff --git a/packages/tourmaline/src/lib/atproto/oauth.ts b/packages/tourmaline/src/lib/atproto/oauth.ts index d3488b0..bf09152 100644 --- a/packages/tourmaline/src/lib/atproto/oauth.ts +++ b/packages/tourmaline/src/lib/atproto/oauth.ts @@ -8,7 +8,8 @@ import { BrowserOAuthClient } from "@atproto/oauth-client-browser"; import { Agent } from "@atproto/api"; -const SCOPE = "atproto repo:app.bsky.feed.post blob:image/png repo:click.croft.toolkit.use"; +const SCOPE = + "atproto repo:app.bsky.feed.post blob:image/png repo:click.croft.toolkit.use"; const CLIENT_ID = import.meta.env.DEV ? `http://localhost?${new URLSearchParams([ diff --git a/packages/tourmaline/src/lib/server/enrich.ts b/packages/tourmaline/src/lib/server/enrich.ts index 85dd8db..c40d74f 100644 --- a/packages/tourmaline/src/lib/server/enrich.ts +++ b/packages/tourmaline/src/lib/server/enrich.ts @@ -16,7 +16,10 @@ const serverCache = new Map(); function getServerCache(name: string): ArtistInfo | null { const entry = serverCache.get(name); if (!entry) return null; - if (Date.now() > entry.exp) { serverCache.delete(name); return null; } + if (Date.now() > entry.exp) { + serverCache.delete(name); + return null; + } return entry.info; } @@ -232,7 +235,9 @@ export async function enrichArtistBatch( const toProcess = artists.slice(0, batchSize); const enriched: Array<{ name: string; info: ArtistInfo }> = []; - console.log(`[tourmaline] enrichArtistBatch: processing ${toProcess.length} artists from a queue of ${artists.length}`); + console.log( + `[tourmaline] enrichArtistBatch: processing ${toProcess.length} artists from a queue of ${artists.length}`, + ); for (const name of toProcess) { // Skip if already enriched in this request or server cache @@ -263,10 +268,15 @@ export async function enrichArtistBatch( }; } } else { - console.log(`[tourmaline] enrichArtistBatch: no MBID found for ${name}`); + console.log( + `[tourmaline] enrichArtistBatch: no MBID found for ${name}`, + ); } } catch (e) { - console.log(`[tourmaline] enrichArtistBatch: MB search error for ${name}:`, e); + console.log( + `[tourmaline] enrichArtistBatch: MB search error for ${name}:`, + e, + ); // MusicBrainz failure is non-critical } @@ -284,10 +294,15 @@ export async function enrichArtistBatch( imageUrl: lfmInfo.imageUrl ?? info.imageUrl, }; } else { - console.log(`[tourmaline] enrichArtistBatch: no Last.fm info for ${name}`); + console.log( + `[tourmaline] enrichArtistBatch: no Last.fm info for ${name}`, + ); } } catch (e) { - console.log(`[tourmaline] enrichArtistBatch: Last.fm error for ${name}:`, e); + console.log( + `[tourmaline] enrichArtistBatch: Last.fm error for ${name}:`, + e, + ); // Last.fm failure is non-critical } } @@ -297,9 +312,15 @@ export async function enrichArtistBatch( try { const dzImage = await dzGetArtistImage(name); if (dzImage) info.imageUrl = dzImage; - else console.log(`[tourmaline] enrichArtistBatch: no Deezer image for ${name}`); + else + console.log( + `[tourmaline] enrichArtistBatch: no Deezer image for ${name}`, + ); } catch (e) { - console.log(`[tourmaline] enrichArtistBatch: Deezer error for ${name}:`, e); + console.log( + `[tourmaline] enrichArtistBatch: Deezer error for ${name}:`, + e, + ); // Deezer failure is non-critical } } diff --git a/packages/tourmaline/src/lib/share/post.ts b/packages/tourmaline/src/lib/share/post.ts index bd95a5f..f4e8175 100644 --- a/packages/tourmaline/src/lib/share/post.ts +++ b/packages/tourmaline/src/lib/share/post.ts @@ -87,13 +87,15 @@ export async function sharePersonality( // 6. Log toolkit usage — best-effort, don't let it fail the share try { await agent.com.atproto.repo.createRecord({ - repo: agent.sessionManager.did ?? agent.did ?? '', - collection: 'click.croft.toolkit.use', + repo: agent.sessionManager.did ?? agent.did ?? "", + collection: "click.croft.toolkit.use", record: { - $type: 'click.croft.toolkit.use', + $type: "click.croft.toolkit.use", tool: { - $type: 'click.croft.tools.tourmaline', - ...(card.totalScrobbles != null ? { scrobblesAnalyzed: card.totalScrobbles } : {}), + $type: "click.croft.tools.tourmaline", + ...(card.totalScrobbles != null + ? { scrobblesAnalyzed: card.totalScrobbles } + : {}), sharedToBluesky: true, }, createdAt: new Date().toISOString(), diff --git a/packages/tourmaline/svelte.config.js b/packages/tourmaline/svelte.config.js index 5b3e7b4..8781aeb 100644 --- a/packages/tourmaline/svelte.config.js +++ b/packages/tourmaline/svelte.config.js @@ -9,7 +9,7 @@ const config = { filename.split(/[/\\]/).includes("node_modules") ? undefined : true, }, kit: { - adapter: adapter({ runtime: 'nodejs22.x' }), + adapter: adapter({ runtime: "nodejs22.x" }), }, }; diff --git a/packages/tourmaline/vercel.json b/packages/tourmaline/vercel.json index d3b556d..63b6273 100644 --- a/packages/tourmaline/vercel.json +++ b/packages/tourmaline/vercel.json @@ -1,5 +1,5 @@ { - "framework": "sveltekit", - "installCommand": "cd ../.. && pnpm install --ignore-scripts --frozen-lockfile --filter @ewanc26/tourmaline...", - "buildCommand": "cd ../.. && pnpm --filter @ewanc26/tourmaline... build" + "framework": "sveltekit", + "installCommand": "cd ../.. && pnpm install --ignore-scripts --frozen-lockfile --filter @ewanc26/tourmaline...", + "buildCommand": "cd ../.. && pnpm --filter @ewanc26/tourmaline... build" } -- 2.51.2