Clone this repository
For self-hosted knots, clone URLs may differ based on your setup.
Download tar.gz
Phase 1 is text-only, so skip converting Bluesky URLs to post embeds.
The social.coves.embed.post lexicon requires a valid CID in strongRef,
which we don't have without calling ResolvePost. This caused P1 bug
where empty CID violated lexicon validation.
Changes:
- Disable tryConvertBlueskyURLToPostEmbed (return false, remove dead code)
- Add TestBlueskyPostCrossPosting_E2E_LivePDS integration test that
writes posts with Bluesky URLs directly to dev PDS to catch lexicon
validation errors
- Create beads for Phase 2 embed conversion (Coves-p44) and functional
options refactoring (Coves-8k1, Coves-jdf, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix quoted post mapping for recordWithMedia#view embeds
- Handle nested viewRecord structure where content is in embed.record.record
- Add mapViewRecordToResult and mapNestedViewRecordToResult functions
- Properly extract text and author from value field (not record field)
- Implement age-based cache TTL decay for scalability
- Fresh posts (< 24h): 15 min TTL (engagement changing rapidly)
- Recent posts (1-7 days): 1 hour TTL
- Old posts (7+ days): 24 hour TTL
- Unavailable posts: 15 min TTL (allow re-checking)
- Add comprehensive unit tests for TTL calculation
- Add integration tests for live Bluesky API interaction
- Update integration tests for new blueskyService parameter
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Enable users to embed Bluesky posts by pasting bsky.app URLs. Posts are
resolved at response time with text, author info, and engagement stats.
## New Package: internal/core/blueskypost/
Core service following the unfurl package pattern:
- types.go: BlueskyPostResult, Author structs with ErrCircuitOpen sentinel
- interfaces.go: Service and Repository interfaces
- repository.go: PostgreSQL cache with TTL (1 hour) and AT-URI validation
- url_parser.go: bsky.app URL → AT-URI conversion with rkey validation
- fetcher.go: Bluesky public API client using SSRF-safe HTTP client
- circuit_breaker.go: Failure protection (3 failures, 5min open)
- service.go: Cache-first resolution with circuit breaker integration
## Features
- Detect bsky.app URLs in post creation, convert to social.coves.embed.post
- Resolve Bluesky posts at feed response time via TransformPostEmbeds()
- Support for quoted posts (1 level deep)
- Media indicators (hasMedia, mediaCount) without rendering (Phase 2)
- Typed error handling with retryable flag for transient failures
- Debug logging for embed processing traceability
## Integration
- Updated discover, timeline, communityFeed handlers
- Wired blueskypost service in cmd/server/main.go
- Database migration for bluesky_post_cache table
## Test Coverage: 73.1%
- url_parser_test.go: URL parsing, validation, edge cases
- circuit_breaker_test.go: State transitions, thread safety
- service_test.go: Cache hit/miss, circuit breaker integration
- fetcher_test.go: Post mapping, media detection, quotes
- repository_test.go: AT-URI validation
## Out of Scope (Phase 2)
- Rendering embedded images/videos
- Moderation labels (NSFW handling)
- Deep quote chains (>1 level nesting)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update GetCommunity() to accept DIDs, canonical handles (c-name.domain),
scoped identifiers (!name@domain), and at-identifiers (@handle)
- Fix subscribe/unsubscribe handlers to let service handle identifier resolution
(removes redundant ResolveCommunityIdentifier calls)
- Add community error handling to aggregator handlers to properly return
404/400 instead of 500 for community errors
- Add communityService dependency to listForCommunity handler for identifier
resolution
- Preserve original identifier in error messages for better debugging
- Add comprehensive unit tests for subscribe/unsubscribe handlers
- Add GetCommunity identifier resolution integration tests
- Add handle format tests for listForCommunity E2E tests
Fixes issue where endpoints only accepted DIDs and rejected valid handles
like c-worldnews.coves.social
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The test was using AddUser() which creates a mock token, but subscription
and other write operations need the real PDS access token. Using
AddUserWithPDSToken() stores the actual PDS token so write-forward works.
All tests now pass without --short flag.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix concurrent modification test to accept either ErrConflict (409)
or CID mismatch error (400 "Record was at")
- Shorten e2epost community name to stay under handle length limit
Remaining: TestFullUserJourney_E2E fails due to PDS token validation
issue ("InvalidToken") - this is a test infrastructure issue where the
community's stored access token is no longer valid with the PDS.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes:
- Exclude deleted top-level comments from ListByParentWithHotRank query
(nested deleted comments still preserved via ListByParentsBatch)
- Fix OAuth E2E tests: unwrap MobileAwareStoreWrapper for cleanup methods
- Fix hostedby security tests: conditionally skip DID verification
- Fix concurrent_scenarios_test: use correct column name (commenter_did)
- Fix user_journey_e2e_test: use correct column name (commenter_did)
- Fix handle length issues: use shorter prefixes with 6-digit timestamps
to stay under ATProto's 32-character handle limit
Pre-commit hook:
- Add go vet check that rejects commits with static analysis issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>