- Add localOnly checkbox to ComposeView, defaulting to checked
- When checked, event is published only to the local ORLY relay
- When unchecked, event is also broadcast to configured relays
via nostrClient.publish() (SimplePool)
- Bump version to v0.58.4
Files modified:
- app/web/src/ComposeView.svelte: Add localOnly prop, checkbox UI, styles
- app/web/src/App.svelte: Wire composeLocalOnly state, conditional broadcast
- app/web/dist/: Rebuilt bundle
- pkg/version/version: v0.58.3 -> v0.58.4
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add recover() and capacity check to prevent crash when event ID slice
has corrupted header (nil data pointer with non-zero length).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix createNIP98AuthHeader and createNIP98Auth in App.svelte to use
userSigner.signEvent() for all auth methods instead of generating
mock signatures for nsec logins
- Add direct GET-based download for open relays (ACL "none") to avoid
browser issues with fetch/blob programmatic downloads
- Add export status feedback (disabled buttons, "Exporting..." text)
- Add cmd/orly-export CLI tool for NIP-98 authenticated relay exports
with progress tracking
- Add vendor/ to .gitignore
- Bump version to v0.58.2
Files modified:
- app/web/src/App.svelte: Fix NIP-98 signing, rewrite export flow
- app/web/src/ExportView.svelte: Add isExporting prop and button states
- app/web/dist/: Rebuilt bundle
- cmd/orly-export/main.go: New CLI export tool
- .gitignore: Add vendor/ exclusion
- pkg/version/version: v0.58.2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
NIP-77 Protocol Fixes:
- Fix role reversal in HandleNegOpen: relay now correctly requires initial
message from client and calls Reconcile() instead of Start()
- Fix premature event transmission: haveIDs events now only sent when
reconciliation completes (complete=true)
- Add proper error handling for missing initial message in NEG-OPEN
Sync Client Mode (gRPC IPC):
- Add --server flag to 'orly sync' for connecting to running service
- Enable one-shot sync without direct database access
- Support remote sync operations via gRPC
- Add filterToProto helper for gRPC communication
Documentation:
- Add NIP77_ANALYSIS_AND_FIX.md with detailed protocol analysis
- Add SYNC_CLIENT_MODE.md with usage examples and architecture diagrams
Bug Fix:
- Fix nil pointer panic in handle-req.go when subscriptions map is uninitialized
- Create negentropy.Handler interface in pkg/interfaces/negentropy
- Add EmbeddedHandler that wraps negentropy.Manager for embedded mode
- Update app/handle-negentropy.go to use interface instead of gRPC client
- Update gRPC client to implement the Handler interface
- Update startup.go to initialize embedded negentropy when not in gRPC mode
- Enables NIP-77 set reconciliation in monolithic binary without gRPC
Files modified:
- pkg/interfaces/negentropy/negentropy.go: New interface definition
- pkg/sync/negentropy/embedded.go: New embedded handler implementation
- pkg/sync/negentropy/grpc/client.go: Implement Handler interface
- app/handle-negentropy.go: Use interface type
- pkg/relay/startup.go: Initialize embedded negentropy
- pkg/version/version: Bump to v0.57.1
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add start/stop/restart buttons for individual services in launcher UI
- Show all available modules with categories (Database, ACL, Sync, Certs)
- Add enable/disable toggles with mutual exclusivity for DB/ACL backends
- Handle service dependencies when stopping (DB stops dependents first)
- Advertise NIP-86 in NIP-11 when ACL mode is managed or curating
- Add module descriptions showing API coverage
Files modified:
- app/handle-relayinfo.go: Add NIP-86 to supported NIPs for managed/curating modes
- cmd/orly-launcher/server.go: Add start-service/stop-service endpoints, ProcessStatus fields
- cmd/orly-launcher/supervisor.go: Add StartService, StopService with dependency handling
- cmd/orly-launcher/web/src/api.js: Add startService, stopService API functions
- cmd/orly-launcher/web/src/components/ProcessCard.svelte: Add toggles, categories, action buttons
- cmd/orly-launcher/web/src/pages/Dashboard.svelte: Add service control handlers
- pkg/version/version: Bump to v0.56.9
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Simplify HandleEvent to thin protocol adapter (~320 -> ~130 lines)
- Create ingestion service for full event pipeline orchestration
- Add specialkinds.go for special kind handler registration
- Eliminate global ACL singleton with injectable Registry interface
- Add ACLRegistry() accessor to Server for dependency injection
- Decompose Rule value object into AccessControl, Constraints,
TagValidationConfig sub-components for cleaner organization
- Add LoggingSubscriber for domain event analytics
- Update all policy tests for embedded struct initialization
- Update DDD_ANALYSIS.md to 10/10 maturity score
Files modified:
- app/handle-event.go: Simplified to delegate to ingestion service
- app/specialkinds.go: NEW - Special kind handler registration
- app/server.go: Add aclRegistry field and ACLRegistry() accessor
- pkg/interfaces/acl/acl.go: Add Registry interface
- pkg/acl/acl.go: Add accessor methods for privatized fields
- pkg/policy/policy.go: Decompose Rule into sub-value objects
- pkg/policy/*_test.go: Update struct literals for embedded types
- pkg/event/ingestion/service.go: Add ACLMode, special kinds support
- pkg/event/processing/processing.go: Add domain event dispatcher
- pkg/domain/events/subscribers/logging.go: NEW - Analytics subscriber
- DDD_ANALYSIS.md: Update to 10/10 maturity score
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use eventenvelope.NewResultWith() to properly format EVENT messages
with subscription ID, matching the expected format: ["EVENT", sub_id, event]
Previously SendEvent was sending just the raw event which caused JSON
parsing errors on the receiving side.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add have_ids, need_ids, complete fields to NegOpenResponse proto
- Fix ID encoding in server: decode hex strings to binary before sending
- Fix event sending order: send NEG-MSG response before EVENT envelopes
- Client now waits for expected events after reconciliation completes
- Store received events from peer in sync client message loop
This enables bidirectional event sync via NIP-77 negentropy. The server
can now push events it has that the client needs, in addition to the
client pushing events to the server.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Implement sendEventsForIDs to actually send events via EVENT envelopes
- Previously this was a TODO stub that only logged "would send"
- Server now fetches and sends identified "have" events to clients
Files modified:
- pkg/version/version: Bump to v0.55.6
- app/handle-negentropy.go: Implement sendEventsForIDs
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add four independent gRPC sync service binaries:
- orly-sync-distributed: Serial-based peer-to-peer sync
- orly-sync-cluster: Cluster replication with persistent state
- orly-sync-relaygroup: Relay group config discovery (Kind 39105)
- orly-sync-negentropy: NIP-77 efficient set reconciliation
- Implement NIP-77 negentropy protocol for relay-to-relay sync
- Add push-based event transfer for negentropy sync
- Add client-facing NIP-77 WebSocket support (NEG-OPEN/MSG/CLOSE)
- Add deployment scripts with symlink-based versioning
- Add Makefile targets for quick deployment and rollback
- Refactor existing sync code into modular service packages
- Add proto definitions for all sync services
- Integrate negentropy service into orly-launcher supervisor
- Advertise NIP-77 support in relay info document
Files modified:
- Makefile: Add sync service build targets and deployment commands
- README.md: Document NIP-77 support
- app/config/config.go: Add negentropy configuration options
- app/handle-message.go: Route NEG-* envelopes to negentropy handler
- app/handle-negentropy.go: New file for NIP-77 WebSocket handling
- app/handle-relayinfo.go: Advertise NIP-77 support
- cmd/orly-launcher/: Add negentropy service management
- cmd/orly-sync-*/: New service binaries
- main.go: Add negentropy gRPC client initialization
- pkg/proto/orlysync/: New proto definitions and generated code
- pkg/sync/*/: Refactored sync packages with gRPC services
- scripts/deploy-orly.sh: New symlink-based deployment script
- scripts/build-and-deploy.sh: New build and deploy workflow
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add query semaphore limiting concurrent database queries to 3
- Reuse single iterator in FetchEventsBySerials instead of one per serial
- Disable expensive e-tag fallback lookup in deletion processing
- Add empty ID validation in access tracker loop
- Reduce log spam from GetSerialById empty ID errors
These changes reduce memory usage from ~5GB to ~150MB under load by
limiting concurrent Badger iterators which consume significant memory.
Files modified:
- pkg/database/database.go: Add query semaphore with acquire/release
- pkg/database/fetch-events-by-serials.go: Reuse iterator for sev lookups
- pkg/database/query-events.go: Add semaphore, disable e-tag fallback
- pkg/database/get-serial-by-id.go: Don't log empty ID errors
- app/handle-req.go: Validate event ID before GetSerialById
- pkg/version/version: Bump to v0.52.17
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Previous: 10MB read + 10MB write = 20MB per connection
New: 64KB read + 64KB write = 128KB per connection
With 100 connections:
- Before: 2GB just for buffers
- After: 12.5MB for buffers
The read limit (SetReadLimit) still allows 10MB messages, but the
internal buffers don't need to be that large.
Files modified:
- app/handle-websocket.go: Reduce buffer sizes
- pkg/version/version: Bump to v0.52.12
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ORLY_QUERY_RESULT_LIMIT config (default 256)
- Enforce limit on each REQ filter before database query
- Caps client limit if it exceeds server limit
- Applies server limit if client sends no limit
This prevents unbounded queries from loading millions of events
into memory and causing OOM kills.
Files modified:
- app/config/config.go: Add QueryResultLimit config
- app/handle-req.go: Enforce limit on each filter
- pkg/version/version: Bump to v0.52.11
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ORLY_MAX_CONN_PER_IP config option (default 25, hard limit 40)
- Track connections per IP in Server struct
- Reject new connections with HTTP 429 when limit exceeded
- Properly decrement count when connections close
This prevents a single IP from opening unlimited connections and
exhausting server memory/goroutines.
Files modified:
- app/config/config.go: Add MaxConnectionsPerIP config
- app/server.go: Add connPerIP tracking map
- app/main.go: Initialize connPerIP map
- app/handle-websocket.go: Implement per-IP limit check
- pkg/version/version: Bump to v0.52.10
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change ORLY_GC_ENABLED default from true to false
- Mark GC as EXPERIMENTAL in config usage string
- Add detailed TODOs documenting Badger race condition issues
- GC triggers "assignment to entry in nil map" panics under concurrent load
Files modified:
- app/config/config.go: Default GC to false, add TODO comments
- pkg/storage/gc.go: Add detailed implementation TODOs
- pkg/version/version: Bump to v0.52.8
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change default event query window from 30 days to 5 years in web UI
- Remove 6-month fallback retry logic (no longer needed with larger window)
- Simplifies fetchAllEvents function in nostr.js
Files modified:
- app/web/src/nostr.js: Extended time window from 30 days to 5 years
- app/web/dist/bundle.js: Rebuilt with changes
- pkg/version/version: Bump to v0.52.7
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add port 7777 to list of known relay ports in config.js
- Prevents false-positive standalone mode detection
Files modified:
- app/web/src/config.js: Add 7777 to relay port list
- pkg/version/version: Bump to v0.52.6
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add standalone mode for dashboard to connect to any ORLY relay
- Implement relay switcher dropdown in header for standalone mode
- Add mobile drawer sidebar at 640px breakpoint with hamburger menu
- Add dedicated fallback pool for profile/relay list/contact list fetches
- Fix relay URL display to show host instead of NIP-11 name
- Add filter validation and defensive checks in event fetching
- Auto-expand search window from 30 days to 6 months on few results
- Add CORS support for API endpoints with configurable origins
- Fetch user relay list (NIP-65 kind 10002) and contact list on login
- Fix light mode user name color visibility in header
Files modified:
- app/config/config.go: Add CORS configuration options
- app/server.go: Add CORS middleware for API endpoints
- app/handle-relayinfo.go: Include CORS in relay info
- app/web/src/config.js: New config module for standalone mode
- app/web/src/stores.js: Add relay URL and standalone mode stores
- app/web/src/nostr.js: Add fallback pool, filter validation, relay/contact fetch
- app/web/src/Header.svelte: Relay dropdown, mobile menu, static indicator
- app/web/src/Sidebar.svelte: Mobile drawer mode with overlay
- app/web/src/App.svelte: Relay switching, mobile menu state, NIP-65 fetch
- app/web/src/api.js: Use configurable base URL
- app/web/src/constants.js: Add fallback relays, dynamic relay URLs
- cmd/dashboard-server/main.go: New standalone dashboard server
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change .kind-number and .delete-target chips to use white text
- Improves contrast on colored backgrounds (cyan/red) in light mode
Files modified:
- app/web/src/EventsView.svelte: Fixed chip text colors
- app/web/dist/: Rebuilt web UI bundle
- pkg/version/version: Bump to v0.52.1
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Improved Cashu token handling and validation
- Better error messages for token verification
- Version bump to v0.51.0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add geteventsforpubkey API method for viewing user events with pagination
- Add deleteeventsforpubkey API method to purge blacklisted user events
- Add clickable user detail view in curation UI showing all events
- Add event content expansion/truncation for long content
- Add kind name display for common Nostr event types
- Implement safety check requiring blacklist before event deletion
Files modified:
- app/handle-nip86-curating.go: Add event fetch/delete handlers
- pkg/database/curating-acl.go: Add GetEventsForPubkey, DeleteEventsForPubkey
- app/web/src/CurationView.svelte: Add user detail view with event listing
- pkg/version/version: Bump to v0.50.0
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix countEventsForPubkey to use SHA256 hash of pubkey (first 8 bytes)
matching the PubHash type used in the Pubkey index
- Fix UI to use event_count field instead of total_events
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Adds "Scan Database" button that calls the scanpubkeys API
- Shows results with total pubkeys, events, and skipped count
- Automatically refreshes the unclassified users list after scan
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ScanAllPubkeys method to scan SerialPubkey index for all pubkeys
- Count events for each pubkey using the Pubkey index
- Store event counts in CURATING_ACL_EVENT_COUNT_ prefix
- Add NIP-86 "scanpubkeys" API endpoint to trigger the scan
This allows the curation UI to show all existing users in the unclassified
list, even if they had events before curating mode was enabled.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The frontend expected 'categories' and 'custom_kinds' but the backend
returned 'kind_categories' and 'allowed_kinds'. Add aliases for both
naming conventions to ensure frontend compatibility.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Change OPTIONAL MATCH to EXISTS subquery for tag filtering in Neo4j
- OPTIONAL MATCH returned rows even when tags didn't match (NULL values)
- EXISTS subquery correctly requires matching tags to exist
- Strip "#" prefix from filter tag types before matching
- Filters use "#d", "#p", "#e" but events store tags without prefix
- Add trace-level logging for Neo4j query debugging
- Add comprehensive tests for Neo4j query builder
- Clean up temporary debug logging from handle-req.go
Files modified:
- pkg/neo4j/query-events.go: Fix tag filtering with EXISTS subquery
- pkg/neo4j/query-events_test.go: Add query builder tests
- app/handle-req.go: Remove debug logging
- pkg/version/version: Bump to v0.49.2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix fetchEvents() discarding IndexedDB cached events instead of merging with relay results
- Add mergeAndDeduplicateEvents() helper to combine and dedupe events by ID
- Add ORLY_BLOSSOM_ENABLED config option to disable Blossom server
- Make fetch-kinds.js fall back to existing eventKinds.js when network unavailable
Files modified:
- app/web/src/nostr.js: Fix event caching, add merge helper
- app/web/scripts/fetch-kinds.js: Add fallback for network failures
- app/config/config.go: Add BlossomEnabled config field
- app/main.go: Check BlossomEnabled before initializing Blossom server
- pkg/version/version: Bump to v0.48.13
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Remove createDefaultProfile() function from nostr.js that auto-created
placeholder profiles for new users - profiles should not be auto-generated
- Add auth-required configuration caution section to CLAUDE.md documenting
risks of enabling NIP-42 auth on production relays
Files modified:
- CLAUDE.md: Added auth-required configuration section
- app/web/src/nostr.js: Removed createDefaultProfile and auto-profile logic
- app/web/dist/bundle.js: Rebuilt with changes
- pkg/version/version: v0.48.11
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add progressive throttle feature for follows ACL mode, allowing
non-followed users to write with increasing delay instead of blocking
- Delay increases linearly per event (default 200ms) and decays at 1:1
ratio with elapsed time, capping at configurable max (default 60s)
- Track both IP and pubkey independently to prevent evasion
- Add periodic cleanup to remove fully-decayed throttle entries
- Fix BBolt serial resolver to return proper errors when buckets or
entries are not found
Files modified:
- app/config/config.go: Add ORLY_FOLLOWS_THROTTLE_* env vars
- app/handle-event.go: Apply throttle delay before event processing
- app/listener.go: Add getFollowsThrottleDelay helper method
- pkg/acl/follows.go: Integrate throttle with follows ACL
- pkg/acl/follows_throttle.go: New progressive throttle implementation
- pkg/bbolt/save-event.go: Return errors from serial lookups
- pkg/version/version: Bump to v0.48.10
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>