See the best posts from any Bluesky account
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rename project from skystar.social to favs.blue

Update all user-facing text, infrastructure config (ClickHouse DB/user,
SQLite filename), code comments, tests, and documentation to reflect the
new favs.blue name.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+57 -57
+2 -2
.env.example
··· 21 21 22 22 # ClickHouse (engagement store) 23 23 CLICKHOUSE_URL=http://localhost:8123 24 - CLICKHOUSE_DB=skystar 25 - CLICKHOUSE_USER=skystar 24 + CLICKHOUSE_DB=favs 25 + CLICKHOUSE_USER=favs 26 26 CLICKHOUSE_PASSWORD= 27 27 28 28 # Jetstream (worker process only)
+1 -1
.env.test
··· 3 3 4 4 # ClickHouse (matches docker-compose dev defaults) 5 5 CLICKHOUSE_URL=http://localhost:8123 6 - CLICKHOUSE_USER=skystar 6 + CLICKHOUSE_USER=favs 7 7 CLICKHOUSE_PASSWORD=
+2 -2
.impeccable.md
··· 8 8 9 9 ### Aesthetic Direction 10 10 - **Tone:** Minimal foundation with playful, deliberate details. Clean enough to feel fast, warm enough to feel human. Small animations that reward interaction — not spectacle, just little moments of delight. 11 - - **Reference:** Bluesky's visual language is a natural touchpoint — the airy feel, the rounded shapes, the blue. skystar.social should feel like it belongs in the same ecosystem without being a clone. It's the fun neighbor, not the corporate sibling. 11 + - **Reference:** Bluesky's visual language is a natural touchpoint — the airy feel, the rounded shapes, the blue. favs.blue should feel like it belongs in the same ecosystem without being a clone. It's the fun neighbor, not the corporate sibling. 12 12 - **Anti-references:** Dense analytics dashboards. Dark-mode-with-neon "hacker" aesthetics. Anything that makes social media feel like work. 13 13 - **Theme:** Light mode primary, dark mode supported. Both should feel equally considered. 14 14 - **Color anchors:** Bluesky blue and heart red as the two emotional poles — discovery and appreciation. Neutrals should feel warm, not clinical. ··· 17 17 1. **Fast to feel, not just fast to load.** Every interaction should feel instant and rewarding. Optimistic UI, snappy transitions, no waiting states that feel like waiting. 18 18 2. **Delight in the details.** The big picture is simple. The magic is in the small things — a subtle animation when a post appears, the way numbers count up, the warmth of the color palette. 19 19 3. **Social media should be fun.** This is a celebration of good posts, not a performance report. The tone is congratulatory, never judgmental. Empty states are encouraging, not clinical. 20 - 4. **Belong to the Bluesky world.** Users should feel at home coming from Bluesky. Respect their visual vocabulary — rounded shapes, airy spacing, familiar blue — while adding skystar's own personality. 20 + 4. **Belong to the Bluesky world.** Users should feel at home coming from Bluesky. Respect their visual vocabulary — rounded shapes, airy spacing, familiar blue — while adding favs.blue's own personality. 21 21 5. **Simple until it isn't.** Start minimal. As features like leaderboards, post-of-the-day, and notifications arrive, the design system should accommodate complexity without losing its lightness.
+3 -3
AGENTS.md
··· 4 4 5 5 ## Project 6 6 7 - skystar.social — a favstar.fm-style Bluesky web app. Given a handle, shows that user's top-25 most-liked or most-reposted posts (all-time / last month). Live engagement comes from the Bluesky Jetstream firehose; first-lookup backfill hits the AppView API. 7 + favs.blue — a favstar.fm-style Bluesky web app. Given a handle, shows that user's top-25 most-liked or most-reposted posts (all-time / last month). Live engagement comes from the Bluesky Jetstream firehose; first-lookup backfill hits the AppView API. 8 8 9 9 ## Stack 10 10 ··· 93 93 94 94 ### Aesthetic Direction 95 95 - **Tone:** Minimal foundation with playful, deliberate details. Clean enough to feel fast, warm enough to feel human. Small animations that reward interaction — not spectacle, just little moments of delight. 96 - - **Reference:** Bluesky's visual language is a natural touchpoint — the airy feel, the rounded shapes, the blue. skystar.social should feel like it belongs in the same ecosystem without being a clone. It's the fun neighbor, not the corporate sibling. 96 + - **Reference:** Bluesky's visual language is a natural touchpoint — the airy feel, the rounded shapes, the blue. favs.blue should feel like it belongs in the same ecosystem without being a clone. It's the fun neighbor, not the corporate sibling. 97 97 - **Anti-references:** Dense analytics dashboards. Dark-mode-with-neon "hacker" aesthetics. Anything that makes social media feel like work. 98 98 - **Theme:** Light mode primary, dark mode supported. Both should feel equally considered. 99 99 - **Color anchors:** Bluesky blue and heart red as the two emotional poles — discovery and appreciation. Neutrals should feel warm, not clinical. ··· 102 102 1. **Fast to feel, not just fast to load.** Every interaction should feel instant and rewarding. Optimistic UI, snappy transitions, no waiting states that feel like waiting. 103 103 2. **Delight in the details.** The big picture is simple. The magic is in the small things — a subtle animation when a post appears, the way numbers count up, the warmth of the color palette. 104 104 3. **Social media should be fun.** This is a celebration of good posts, not a performance report. The tone is congratulatory, never judgmental. Empty states are encouraging, not clinical. 105 - 4. **Belong to the Bluesky world.** Users should feel at home coming from Bluesky. Respect their visual vocabulary — rounded shapes, airy spacing, familiar blue — while adding skystar's own personality. 105 + 4. **Belong to the Bluesky world.** Users should feel at home coming from Bluesky. Respect their visual vocabulary — rounded shapes, airy spacing, familiar blue — while adding favs.blue's own personality. 106 106 5. **Simple until it isn't.** Start minimal. As features like leaderboards, post-of-the-day, and notifications arrive, the design system should accommodate complexity without losing its lightness.
+2 -2
README.md
··· 1 - # skystar.social 1 + # favs.blue 2 2 3 - skystar.social is a favstar.fm-style web app for Bluesky: type any handle and see that user's top-25 most-liked or most-reposted posts, filterable by "all time" or "last month." It works by subscribing to the Bluesky Jetstream firehose for live engagement events, and running a one-time backfill on first lookup against the Bluesky AppView API. The stack is TypeScript / AdonisJS v7 (web + workers), ClickHouse for the append-only engagement store, and SQLite for metadata. Everything ships as a single Docker image with three process entrypoints: an HTTP web server, a Jetstream WebSocket consumer, and a queue worker that runs backfill jobs. 3 + favs.blue is a favstar.fm-style web app for Bluesky: type any handle and see that user's top-25 most-liked or most-reposted posts, filterable by "all time" or "last month." It works by subscribing to the Bluesky Jetstream firehose for live engagement events, and running a one-time backfill on first lookup against the Bluesky AppView API. The stack is TypeScript / AdonisJS v7 (web + workers), ClickHouse for the append-only engagement store, and SQLite for metadata. Everything ships as a single Docker image with three process entrypoints: an HTTP web server, a Jetstream WebSocket consumer, and a queue worker that runs backfill jobs. 4 4 5 5 ## Local development 6 6
+1 -1
app/lib/atproto/client.ts
··· 474 474 * exhausted, the entire call throws and any successfully-fetched results from 475 475 * earlier batches are discarded. The caller is responsible for checkpointing 476 476 * if they need progress preservation. See spec §6 Flow 2 for the 477 - * checkpoint-and-resume backfill model used by the skystar backfill loop. 477 + * checkpoint-and-resume backfill model used by the favs.blue backfill loop. 478 478 * 479 479 * @param uris - AT-URIs of posts to hydrate 480 480 * @returns PostView objects with likeCount, repostCount, quoteCount
+1 -1
app/lib/atproto/parsers/jetstream.ts
··· 450 450 * Parse a raw Jetstream JSON event (already parsed from string) into a typed 451 451 * internal event shape, or return null if: 452 452 * - The event is malformed / not an object 453 - * - The kind/collection is one skystar v1 doesn't care about 453 + * - The kind/collection is one favs.blue v1 doesn't care about 454 454 * 455 455 * **Like and repost DELETE events return null intentionally.** 456 456 * Per spec §10: "Unlikes / unreposts are not tracked. Counts may drift up
+1 -1
app/lib/atproto/types.ts
··· 131 131 } 132 132 133 133 /** 134 - * Discriminated union of all Jetstream event types skystar cares about. 134 + * Discriminated union of all Jetstream event types favs.blue cares about. 135 135 */ 136 136 export type JetstreamEvent = 137 137 | LikeEvent
+1 -1
app/lib/clickhouse/store.ts
··· 106 106 107 107 /** 108 108 * Thin wrapper around @clickhouse/client that owns all ClickHouse SQL in 109 - * the skystar project. Accepts config via constructor injection so tests can 109 + * the favs.blue project. Accepts config via constructor injection so tests can 110 110 * point it at a dedicated test database. 111 111 * 112 112 * The underlying client manages its own connection pool — do not create
+11 -11
docker-compose.yml
··· 4 4 volumes: 5 5 - clickhouse-data:/var/lib/clickhouse 6 6 environment: 7 - CLICKHOUSE_DB: skystar 8 - CLICKHOUSE_USER: skystar 7 + CLICKHOUSE_DB: favs 8 + CLICKHOUSE_USER: favs 9 9 CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} 10 10 ports: 11 11 - "127.0.0.1:8123:8123" ··· 31 31 PORT: 3333 32 32 HOST: 0.0.0.0 33 33 DB_CONNECTION: sqlite 34 - SQLITE_PATH: /data/skystar.sqlite 34 + SQLITE_PATH: /data/favs.sqlite 35 35 CLICKHOUSE_URL: http://clickhouse:8123 36 - CLICKHOUSE_DB: skystar 37 - CLICKHOUSE_USER: skystar 36 + CLICKHOUSE_DB: favs 37 + CLICKHOUSE_USER: favs 38 38 CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} 39 39 APP_KEY: ${APP_KEY} 40 40 APP_URL: http://localhost:3333 ··· 64 64 environment: 65 65 NODE_ENV: production 66 66 DB_CONNECTION: sqlite 67 - SQLITE_PATH: /data/skystar.sqlite 67 + SQLITE_PATH: /data/favs.sqlite 68 68 CLICKHOUSE_URL: http://clickhouse:8123 69 - CLICKHOUSE_DB: skystar 70 - CLICKHOUSE_USER: skystar 69 + CLICKHOUSE_DB: favs 70 + CLICKHOUSE_USER: favs 71 71 CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} 72 72 JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe 73 73 APP_KEY: ${APP_KEY} ··· 89 89 environment: 90 90 NODE_ENV: production 91 91 DB_CONNECTION: sqlite 92 - SQLITE_PATH: /data/skystar.sqlite 92 + SQLITE_PATH: /data/favs.sqlite 93 93 CLICKHOUSE_URL: http://clickhouse:8123 94 - CLICKHOUSE_DB: skystar 95 - CLICKHOUSE_USER: skystar 94 + CLICKHOUSE_DB: favs 95 + CLICKHOUSE_USER: favs 96 96 CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} 97 97 APP_KEY: ${APP_KEY} 98 98 APP_URL: http://localhost:3333
+14 -14
docs/superpowers/specs/2026-04-11-skystar-bluesky-design.md
··· 1 - # Skystar.social — Design Spec 1 + # favs.blue — Design Spec 2 2 3 3 **Date:** 2026-04-11 4 4 **Status:** Approved, ready for implementation planning ··· 8 8 9 9 ## 1. Goal and scope 10 10 11 - Skystar.social lets a visitor type any Bluesky handle and see that user's 11 + favs.blue lets a visitor type any Bluesky handle and see that user's 12 12 top-25 most-liked or most-reposted posts, filterable by time window. The 13 13 experience is modeled on the original favstar.fm for Twitter (2009-2018): 14 14 unauthenticated, instant on repeat visits, focused on a single greatest-hits ··· 174 174 ### Repository layout 175 175 176 176 ``` 177 - skystar/ 177 + favs.blue/ 178 178 ├── app/ 179 179 │ ├── controllers/ # ProfileController, SearchController 180 180 │ ├── jobs/ # BackfillJob (@adonisjs/queue Job) ··· 783 783 volumes: 784 784 - clickhouse-data:/var/lib/clickhouse 785 785 environment: 786 - CLICKHOUSE_DB: skystar 787 - CLICKHOUSE_USER: skystar 786 + CLICKHOUSE_DB: favs 787 + CLICKHOUSE_USER: favs 788 788 CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} 789 789 ulimits: 790 790 nofile: 262144 ··· 799 799 NODE_ENV: production 800 800 PORT: 3333 801 801 DB_CONNECTION: sqlite 802 - SQLITE_PATH: /data/skystar.sqlite 802 + SQLITE_PATH: /data/favs.sqlite 803 803 CLICKHOUSE_URL: http://clickhouse:8123 804 - CLICKHOUSE_DB: skystar 805 - CLICKHOUSE_USER: skystar 804 + CLICKHOUSE_DB: favs 805 + CLICKHOUSE_USER: favs 806 806 CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} 807 807 APP_KEY: ${APP_KEY} 808 808 ports: ··· 817 817 environment: 818 818 NODE_ENV: production 819 819 DB_CONNECTION: sqlite 820 - SQLITE_PATH: /data/skystar.sqlite 820 + SQLITE_PATH: /data/favs.sqlite 821 821 CLICKHOUSE_URL: http://clickhouse:8123 822 - CLICKHOUSE_DB: skystar 823 - CLICKHOUSE_USER: skystar 822 + CLICKHOUSE_DB: favs 823 + CLICKHOUSE_USER: favs 824 824 CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} 825 825 JETSTREAM_URL: wss://jetstream2.us-east.bsky.network/subscribe 826 826 ··· 833 833 environment: 834 834 NODE_ENV: production 835 835 DB_CONNECTION: sqlite 836 - SQLITE_PATH: /data/skystar.sqlite 836 + SQLITE_PATH: /data/favs.sqlite 837 837 CLICKHOUSE_URL: http://clickhouse:8123 838 - CLICKHOUSE_DB: skystar 839 - CLICKHOUSE_USER: skystar 838 + CLICKHOUSE_DB: favs 839 + CLICKHOUSE_USER: favs 840 840 CLICKHOUSE_PASSWORD: ${CLICKHOUSE_PASSWORD} 841 841 842 842 volumes:
+1 -1
package.json
··· 1 1 { 2 - "name": "skystar", 2 + "name": "favs.blue", 3 3 "version": "0.0.0", 4 4 "private": true, 5 5 "type": "module",
+3 -3
resources/views/components/layout.edge
··· 17 17 @if($slots.title) 18 18 {{{ await $slots.title() }}} 19 19 @else 20 - skystar — bluesky's greatest hits 20 + favs.blue — bluesky's greatest hits 21 21 @endif 22 22 </title> 23 23 @if(typeof canonicalUrl !== 'undefined' && canonicalUrl) ··· 30 30 <div class="flex items-center justify-between pt-6 pb-4"> 31 31 <a href="/" class="flex items-center gap-1.5 text-gray-900 dark:text-gray-100 no-underline hover:opacity-70"> 32 32 <i class="ph-fill ph-heart text-red-500 text-lg"></i> 33 - <span class="font-semibold text-sm">skystar</span> 33 + <span class="font-semibold text-sm">favs.blue</span> 34 34 </a> 35 35 <div class="flex items-center gap-2"> 36 36 <form action="/search" method="GET"> ··· 51 51 @endif 52 52 {{{ await $slots.main() }}} 53 53 <footer class="mt-12 py-6 border-t border-gray-200 dark:border-gray-800 text-[13px] text-gray-500 dark:text-gray-400 text-center"> 54 - <a href="/" class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 hover:underline">skystar.social</a> &middot; 54 + <a href="/" class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 hover:underline">favs.blue</a> &middot; 55 55 <a href="/about" class="text-blue-600 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 hover:underline">about</a> 56 56 </footer> 57 57 </div>
+5 -5
resources/views/pages/about.edge
··· 1 1 @component('components/layout') 2 2 @slot('title') 3 - About — skystar 3 + About — favs.blue 4 4 @endslot 5 5 6 6 @slot('main') 7 7 <div class="py-12 space-y-4"> 8 - <h1 class="text-3xl font-bold mb-6">About skystar</h1> 8 + <h1 class="text-3xl font-bold mb-6">About favs.blue</h1> 9 9 <p> 10 - skystar is a <a href="https://favstar.fm" target="_blank" rel="noopener" class="text-blue-600 dark:text-blue-400 hover:underline">favstar.fm</a>-style 10 + favs.blue is a <a href="https://favstar.fm" target="_blank" rel="noopener" class="text-blue-600 dark:text-blue-400 hover:underline">favstar.fm</a>-style 11 11 site for <a href="https://bsky.app" target="_blank" rel="noopener" class="text-blue-600 dark:text-blue-400 hover:underline">Bluesky</a>. 12 12 Type any handle and see that account's most-liked and most-reposted posts, powered by the 13 13 <a href="https://atproto.com" target="_blank" rel="noopener" class="text-blue-600 dark:text-blue-400 hover:underline">AT Protocol</a> firehose. ··· 18 18 </p> 19 19 <p> 20 20 Deletions and account takedowns are honored: when a post or account is removed from 21 - Bluesky, it disappears from skystar too. 21 + Bluesky, it disappears from favs.blue too. 22 22 </p> 23 23 <p> 24 24 {{-- TODO: add GitHub URL --}} 25 - Source code: <a href="https://github.com/TODO/skystar" target="_blank" rel="noopener" class="text-blue-600 dark:text-blue-400 hover:underline">github.com/TODO/skystar</a> 25 + Source code: <a href="https://github.com/TODO/favs.blue" target="_blank" rel="noopener" class="text-blue-600 dark:text-blue-400 hover:underline">github.com/TODO/favs.blue</a> 26 26 </p> 27 27 <p><a href="/" class="text-blue-600 dark:text-blue-400 hover:underline">← Back to search</a></p> 28 28 </div>
+1 -1
resources/views/pages/errors/not_found.edge
··· 1 1 @component('components/layout') 2 2 @slot('title') 3 - Not found — skystar 3 + Not found — favs.blue 4 4 @endslot 5 5 6 6 @slot('main')
+1 -1
resources/views/pages/errors/server_error.edge
··· 1 1 @component('components/layout') 2 2 @slot('title') 3 - Server error — skystar 3 + Server error — favs.blue 4 4 @endslot 5 5 6 6 @slot('main')
+2 -2
resources/views/pages/landing.edge
··· 2 2 @slot('main') 3 3 <div class="pt-16 pb-12"> 4 4 <div class="flex items-center justify-between mb-2"> 5 - <h1 class="text-3xl font-bold">skystar <span class="text-blue-500">✦</span></h1> 5 + <h1 class="text-3xl font-bold">favs.blue <span class="text-blue-500">✦</span></h1> 6 6 <button 7 7 x-data="darkMode" 8 8 x-on:click="toggle" ··· 34 34 </p> 35 35 36 36 <p class="text-sm text-gray-500 dark:text-gray-400 max-w-[520px]"> 37 - skystar indexes Bluesky posts and tracks engagement from the 37 + favs.blue indexes Bluesky posts and tracks engagement from the 38 38 <a href="https://atproto.com" target="_blank" rel="noopener" class="text-blue-600 dark:text-blue-400 hover:underline">AT Protocol</a> firehose. 39 39 Type a handle to see that account's greatest hits. 40 40 No login required.
+1 -1
resources/views/pages/profile/gone.edge
··· 1 1 @component('components/layout') 2 2 @slot('title') 3 - {{ '@' + handle }} — skystar 3 + {{ '@' + handle }} — favs.blue 4 4 @endslot 5 5 6 6 @slot('main')
+1 -1
resources/views/pages/profile/loading.edge
··· 7 7 @endslot 8 8 9 9 @slot('title') 10 - Indexing {{ '@' + handle }} — skystar 10 + Indexing {{ '@' + handle }} — favs.blue 11 11 @endslot 12 12 13 13 @slot('main')
+1 -1
resources/views/pages/profile/show.edge
··· 1 1 @component('components/layout', { canonicalUrl }) 2 2 @slot('title') 3 - Top {{ kind === 'likes' ? 'liked' : 'reposted' }} posts of {{ '@' + handle }} — skystar 3 + Top {{ kind === 'likes' ? 'liked' : 'reposted' }} posts of {{ '@' + handle }} — favs.blue 4 4 @endslot 5 5 6 6 @slot('main')
+1 -1
tests/functional/profile_controller.spec.ts
··· 89 89 test('GET / returns 200 with landing page', async ({ client, assert }) => { 90 90 const response = await client.get('/') 91 91 response.assertStatus(200) 92 - assert.include(response.text(), 'skystar') 92 + assert.include(response.text(), 'favs.blue') 93 93 }) 94 94 95 95 // Test 2: GET /about returns 200
+1 -1
tests/unit/lucid_schemas.spec.ts
··· 6 6 import BackfillJob from '#models/backfill_job' 7 7 8 8 /** 9 - * Verify the three skystar Lucid models can be created after migrations run. 9 + * Verify the three favs.blue Lucid models can be created after migrations run. 10 10 * These tests run migrations against a temp SQLite database and confirm the 11 11 * schema is wired correctly. 12 12 */