commits
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
run `just setup` after cloning to install the hook.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
new stats tracked:
- quote_matches: posts that matched via quoted content
- multi_platform: posts with links to 2+ platforms
- match rate: calculated posts/hour
dashboard now shows insights section with these metrics.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
turbostream hydrates quoted post content, so we can now detect
music links in posts that quote other music posts.
checks:
- embed.record.value.embed.external.uri (quoted post link cards)
- embed.record.value.facets (quoted post inline links)
- embed.record.record.value... (recordWithMedia variant)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add PlatformSet to track unique platforms per post
- detectAllPlatformsFromRecord returns all platforms found
- posts with multiple platform links now count each platform
- remove old single-platform detection function
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- filter.zig: add music.apple.com domain detection
- stats.zig: add apple platform counter
- dashboard.zig: display apple music in platforms list
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- store/db.zig: sqlite connection and schema only
- store/posts.zig: post CRUD with cursor pagination
- store/follows.zig: follows cache operations
- store/backfill.zig: backfill completion tracking
- bsky/auth.zig: JWT verification (from atproto.zig)
- bsky/api.zig: bluesky API calls (from atproto.zig)
- feed/skeleton.zig: feed response JSON building
- feed/backfill.zig: backfill orchestration
- jetstream.zig: moved to top level (was stream/)
removes 800+ lines of tangled code from stream/db.zig
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 7 tests: filter detection, stats counters, status logic
- ci: lint, build, test on ubuntu/macos matrix
- pre-commit hook now runs zig build
- simplify filter.zig json navigation with zat.json helpers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
removes ~240 lines of hand-rolled JWT/crypto code:
- base58 decoding
- multibase/multicodec parsing
- ECDSA signature verification
- base64url decoding
now uses zat.Jwt.parse() + jwt.verify() + zat.DidResolver
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add thread_safe = true to GPA (required for multi-threaded usage)
- use detach() instead of misleading defer join() (consumer never returns)
- switch from debug.print to std.log.scoped for proper log levels
- group imports: stdlib → external → internal
patterns verified against ghostty, tigerbeetle, zap, http.zig
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
notes from building this feed generator - memory management,
json handling, concurrency patterns, sdk adoption
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
uses comptime struct extraction instead of manual path navigation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
src/
├── feed/ ← customize these for your feed
│ ├── filter.zig
│ └── config.zig
├── server/ ← web layer
│ ├── http.zig
│ ├── dashboard.zig
│ └── stats.zig
└── stream/ ← ingestion
├── jetstream.zig
├── db.zig
└── atproto.zig
also: add pre-commit hook for zig fmt
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- replace manual DID resolution with zat.DidResolver
- replace raw HTTP/TLS code with zat.XrpcClient
- use zat.json helpers for nested JSON navigation
- atproto.zig: 898 → 454 lines (50% reduction)
remaining in atproto.zig: JWT parsing and ECDSA signature verification
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- zat now exports Tid, AtUri, etc. at top level instead of internal namespace
- use zat.Tid.parse() instead of zat.internal.Tid.parse()
- update readme dashboard link to custom domain
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- replace hand-rolled TID parsing with zat.internal.Tid
- replace manual AT-URI parsing with zat.internal.AtUri
- cleaner, more correct (zat validates TID first char high bit)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add bluesky butterfly icon to open feeds
- show "<1%" for platforms with non-zero but sub-1% share
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- track soundcloud/bandcamp/spotify/plyr.fm matches separately
- show platform breakdown with color-coded bar charts on dashboard
- fix terminology: turbostream is a stream provider, not a relay
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
turbostream provides hydrated metadata (user profiles, parent posts)
without additional API calls. same core logic, different data source.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add /stats endpoint with lag, uptime, post count
- redesign dashboard: feeds prominent, status bar with live indicator
- add periodic jetstream logging (every 30s)
- track actual post creation time from TID for accurate lag
- deduplicate parseTidTimestamp (now in db.zig)
- update readme and docs for two-feed setup
- add scripts/feed_status.py CLI for checking feed health
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
the multicodec prefix for secp256k1 (0xe7 = 231) is actually 2 bytes
(0xe7 0x01) because values >= 128 require LEB128 continuation bytes.
we were only skipping 1 byte, passing a 34-byte "key" instead of the
correct 33-byte compressed public key.
also adds docs/atproto-crypto.md with notes on AT Protocol JWT auth.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
backfill was only checking post text, missing embed-only link cards
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
posts with link cards (no inline text link) weren't being captured
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This reverts commit be87e001e3c3d95261cbb92f989de21c968192bc.
posts indexed before TID parsing was added had incorrect timestamps
(set to indexing time instead of actual post time). this migration
runs on startup and corrects any posts where stored timestamp differs
from TID-derived timestamp by more than 1 hour.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add FeedType enum (all/following) to support two feeds
- implement follows cache with 5min TTL in sqlite
- parse TID timestamps from AT URIs for correct chronological ordering
- lazy backfill historical posts when user's follows are fetched
- update dashboard to show both feeds
- add getFollows and getAuthorFeed API calls
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
a bluesky feed that surfaces posts with music links (soundcloud, bandcamp, plyr.fm)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
new stats tracked:
- quote_matches: posts that matched via quoted content
- multi_platform: posts with links to 2+ platforms
- match rate: calculated posts/hour
dashboard now shows insights section with these metrics.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
turbostream hydrates quoted post content, so we can now detect
music links in posts that quote other music posts.
checks:
- embed.record.value.embed.external.uri (quoted post link cards)
- embed.record.value.facets (quoted post inline links)
- embed.record.record.value... (recordWithMedia variant)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add PlatformSet to track unique platforms per post
- detectAllPlatformsFromRecord returns all platforms found
- posts with multiple platform links now count each platform
- remove old single-platform detection function
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- store/db.zig: sqlite connection and schema only
- store/posts.zig: post CRUD with cursor pagination
- store/follows.zig: follows cache operations
- store/backfill.zig: backfill completion tracking
- bsky/auth.zig: JWT verification (from atproto.zig)
- bsky/api.zig: bluesky API calls (from atproto.zig)
- feed/skeleton.zig: feed response JSON building
- feed/backfill.zig: backfill orchestration
- jetstream.zig: moved to top level (was stream/)
removes 800+ lines of tangled code from stream/db.zig
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- 7 tests: filter detection, stats counters, status logic
- ci: lint, build, test on ubuntu/macos matrix
- pre-commit hook now runs zig build
- simplify filter.zig json navigation with zat.json helpers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
removes ~240 lines of hand-rolled JWT/crypto code:
- base58 decoding
- multibase/multicodec parsing
- ECDSA signature verification
- base64url decoding
now uses zat.Jwt.parse() + jwt.verify() + zat.DidResolver
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add thread_safe = true to GPA (required for multi-threaded usage)
- use detach() instead of misleading defer join() (consumer never returns)
- switch from debug.print to std.log.scoped for proper log levels
- group imports: stdlib → external → internal
patterns verified against ghostty, tigerbeetle, zap, http.zig
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
src/
├── feed/ ← customize these for your feed
│ ├── filter.zig
│ └── config.zig
├── server/ ← web layer
│ ├── http.zig
│ ├── dashboard.zig
│ └── stats.zig
└── stream/ ← ingestion
├── jetstream.zig
├── db.zig
└── atproto.zig
also: add pre-commit hook for zig fmt
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- replace manual DID resolution with zat.DidResolver
- replace raw HTTP/TLS code with zat.XrpcClient
- use zat.json helpers for nested JSON navigation
- atproto.zig: 898 → 454 lines (50% reduction)
remaining in atproto.zig: JWT parsing and ECDSA signature verification
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- track soundcloud/bandcamp/spotify/plyr.fm matches separately
- show platform breakdown with color-coded bar charts on dashboard
- fix terminology: turbostream is a stream provider, not a relay
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add /stats endpoint with lag, uptime, post count
- redesign dashboard: feeds prominent, status bar with live indicator
- add periodic jetstream logging (every 30s)
- track actual post creation time from TID for accurate lag
- deduplicate parseTidTimestamp (now in db.zig)
- update readme and docs for two-feed setup
- add scripts/feed_status.py CLI for checking feed health
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
the multicodec prefix for secp256k1 (0xe7 = 231) is actually 2 bytes
(0xe7 0x01) because values >= 128 require LEB128 continuation bytes.
we were only skipping 1 byte, passing a 34-byte "key" instead of the
correct 33-byte compressed public key.
also adds docs/atproto-crypto.md with notes on AT Protocol JWT auth.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
posts indexed before TID parsing was added had incorrect timestamps
(set to indexing time instead of actual post time). this migration
runs on startup and corrects any posts where stored timestamp differs
from TID-derived timestamp by more than 1 hour.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- add FeedType enum (all/following) to support two feeds
- implement follows cache with 5min TTL in sqlite
- parse TID timestamps from AT URIs for correct chronological ordering
- lazy backfill historical posts when user's follows are fetched
- update dashboard to show both feeds
- add getFollows and getAuthorFeed API calls
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>