## Summary
- add structured HTTP and gRPC access logs with request correlation,
status-aware levels, response bytes, and complete body lifecycle timing
- route diagnostic and access events through independently configurable
text or JSON outputs with non-blocking writers and dropped-line
accounting
- honor the configured IANA timezone across text, JSON, diagnostic, and
access logs with DST-aware RFC 3339 offsets
- keep HTTP metric labels bounded while logging concrete unmatched paths
without query strings
- reduce local development noise by defaulting global logs to info while
retaining component debug logs
## Behavior
- HTTP 2xx and 3xx complete at info, 4xx at warn, and 5xx or body
failures at error
- slow handlers are raised to warn; ordinary client response
cancellation remains debug
- gRPC completion waits for final trailers and records the canonical
gRPC status
- request IDs are validated, propagated in responses, and included in
mapped gRPC errors
- header latency and full response lifecycle latency are reported
separately
- log timestamps use the configured time.timezone and include a numeric
UTC offset
## Testing
- cargo test -p synctv-api-common transport_access_log --lib
- cargo test -p synctv-core logging::tests --lib
- cargo test -p synctv resource_options::tests --lib
- cargo clippy for affected crates and all targets with warnings denied
- cargo check --workspace --all-targets
- cargo fmt --all -- --check
- git diff --check
- local server smoke tests for request completion, request ID
propagation, byte counts, query omission, and Asia/Shanghai timestamps
in diagnostic and access logs
## Summary
- bump the server, Helm chart, and documentation metadata to v1.0.3
- keep the Compose image fallback validation aligned with the
configurable image tag
- release the latest upstream main commit with personal user blocking
## Verification
- `make fmt-check`
- `make validate-helm`
- `cargo test -p synctv-api-common user_block --locked`
## Summary
- add idempotent block, unblock, and paginated block-list APIs across
HTTP, gRPC, service, and repository layers
- hide blocked creators from authenticated personal discovery while
leaving anonymous discovery unchanged
- preserve joined, favorited, and directly accessed rooms with a
creator_blocked marker
- filter blocked users from message history, search, context, pinned
messages, replay, unread counts, live events, and reconnect replay
- add the user_blocks migration, SQLx offline metadata, and
repository/API coverage
## Behavior
Blocking is private to the current account. It does not alter the
blocked account or public anonymous discovery. Detailed personal room
lists remain complete and expose creator_blocked so clients can label
the creator. Observer-specific realtime filtering advances event cursors
and fails closed when block-state lookup is unavailable.
## Verification
- make check
- make fmt-check
- cargo test --workspace: 5222 passed
- synctv-api-common library tests: 638 passed, 165 ignored
- PostgreSQL favorite-room creator_blocked integration test
- SQLx prepare across the workspace
- real two-account HTTP, realtime, and room-flow integration test
Client: https://github.com/synctv-org/synctv-app/pull/47
## Summary
- disable the built-in SyncTV STUN server by default across application,
Compose, image, environment, and Helm configuration
- default `webrtc.externalIceServers` to Cloudflare and Google public
STUN endpoints while preserving persisted overrides and explicit `[]`
- update deployment validation, tests, examples, and English/Chinese
documentation
Tailscale is not included because it publishes STUN endpoints through
its dynamic DERP map and does not provide a stable general-purpose
hostname suitable for this default.
## Testing
- `cargo fmt --all -- --check`
- `cargo test -p synctv-core --test global_settings_registry_tests`
- `cargo test -p synctv-core
test_runtime_settings_default_to_public_stun_servers`
- `cargo test -p synctv default_webrtc_config_disables_builtin_stun`
- `cargo check --workspace --all-targets`
- `bash scripts/validate-helm.sh`
- Compose configuration rendering
- `npm run validate:content`
- `npm run check`
- `git diff --check`
## Summary
- split room, node, and global presence into authenticated member and
anonymous guest counts
- deduplicate member and guest actors independently while retaining
total connection counts
- update discovery ordering and realtime online-count messages to use
the separated values
- remove the previous mixed online-user field as a breaking protocol
change
Frontend: https://github.com/synctv-org/synctv-app/pull/41
## Verification
- make nextest-default (5231 passed)
- make clippy-check
- make proto-freshness
- cargo fmt --all -- --check
- git diff --check
## Summary
- allow `synctv room visibility` to omit the actor and run as an
authenticated management-plane operation
- preserve optional `--username`, `--user-id`, and `--email` actor
selection for normal room permission enforcement
- keep the permission bypass confined to the management service and
reuse the same visibility update, cache invalidation, and guest
revocation behavior
Follow-up to #419.
## Validation
- `cargo fmt --all -- --check`
- `cargo check -p synctv-core -p synctv-management -p synctv`
- `cargo clippy -p synctv-core -p synctv-management -p synctv
--all-targets -- -D warnings`
- `cargo test -p synctv room_visibility` (3 passed)
- verified `synctv room visibility --help` shows actor selectors as
optional and still requires exactly one of `--public` or `--private`
## Summary
- store room public visibility as a first-class database field
independent of password and guest settings
- expose visibility through client, management, HTTP, gRPC, CLI,
discovery, and room creation review flows
- add `synctv room visibility ROOM_ID --public|--private` for changing
existing rooms with an explicit actor
- deny anonymous guest access to private rooms while preserving
authenticated member access
- revoke existing guest sessions immediately when a room becomes private
without disconnecting authenticated members
## Validation
- `cargo fmt --all -- --check`
- `cargo clippy -p synctv-core -p synctv-api-common -p synctv-api-http
-p synctv-api-grpc -p synctv --all-targets -- -D warnings`
- `cargo clippy -p synctv-management -p synctv --all-targets -- -D
warnings`
- `cargo check -p synctv-core -p synctv-api-common -p synctv-api-http -p
synctv-api-grpc -p synctv`
- `cargo check -p synctv-management -p synctv`
- `cargo test -p synctv room_visibility`
- verified `synctv room visibility --help` renders the required actor
and exactly one of `--public` or `--private`
- targeted realtime bridge, guest principal, realtime event, and room
service tests
- real macOS client verification: changing a public room to private
terminated the guest connection in about two seconds with
`REALTIME_TERMINATION_CODE_GUEST_ACCESS_REVOKED`; authenticated users
remained connected
## Changes
- Remove PostgreSQL and Redis host port publications from production and
development Compose files.
- Keep application connectivity through the existing Compose networks
and service names.
## Validation
- docker compose -f docker-compose.dev.yml config --quiet
- git diff --check
## Summary
- Treat explicit null collection fields in Bilibili VOD playback
responses as empty collections.
- Cover DASH streams, backup URLs, Dolby audio, supported formats, DURL
responses, subtitles, video pages, UGC collections, and PGC episodes.
- Resolve Bilibili DASH resources from versioned playback metadata and
provide every equivalent CDN URL to the existing candidate executor.
- Prefer the URL selected by the player, then retry primary and backup
CDNs before committing the downstream response.
## Root cause
Bilibili occasionally emits explicit null for fields normally
represented as JSON arrays, which caused playback/start deserialization
errors. Proxy DASH resource requests also used a single CDN even though
the provider response included backup URLs.
## Validation
- cargo fmt --all -- --check
- cargo check for synctv-api-common, synctv-api-http, and
synctv-api-grpc
- synctv-media-providers Bilibili tests: 91 passed
- synctv-core Bilibili tests: 17 passed, 2 ignored
- candidate executor tests: 2 passed
- Real playback/start, proxy MPD, Range request, CDN fault injection,
dev build, and dev smoke checks
Fix OIDC ID Token validation when the provider returns an issuer with a
trailing slash.
SyncTV removes trailing slashes from the configured issuer, but
`jsonwebtoken` previously performed an exact issuer check before
SyncTV's normalized issuer comparison. This caused valid tokens from
providers such as Authentik to fail with `InvalidIssuer`.
The JWT validation now accepts both trailing-slash variants of the same
issuer while continuing to reject unrelated issuers.
Added RS256/JWKS regression tests covering both accepted configurations
and a different-issuer rejection case.
## Summary
- remove the `dev-start` and `dev-stop` Makefile targets and related
documentation
- keep `dev-serve` as the foreground development server entry point
- make the provider smoke test own and clean up its server process
- keep `dev-down` and `dev-clean` scoped to Compose services
## Verification
- `bash -n scripts/dev-e2e-smoke.sh`
- Makefile dry runs for `dev-serve`, `dev-down`, and `dev-clean`
- `make help` target inspection
- `git diff --check`
- existing development backend health check
## Changes
- roll back local and Redis reservations when a settings transaction
aborts
- atomically reconcile stale committed fences against the database
version
- repair runtime-setting fences during startup and before writes
- retry paths now refresh fence state after SQL optimistic-lock
conflicts
## Verification
- cargo test -p synctv-core --lib
- cargo check --workspace
- cargo clippy -p synctv-core -p synctv-api-common --all-targets -- -D
warnings
- real HTTP regression with Redis fence seeded above the database
version
## Summary
- send a typed realtime termination message before server-initiated
WebSocket disconnects
- restore the view_playback_history permission bit across effective
permissions and runtime settings round trips
- remove the unused common ErrorCode enum and document the dedicated
termination codes
## Validation
- make nextest: 6715 passed
- make clippy
- git diff --check
## Summary
- Add recoverable soft deletion for users, rooms, playlists, media, and
chat messages, with retention-based physical cleanup.
- Hide deleted users and owned resources from active API, room,
playback, messaging, and realtime paths while preserving restoration
metadata.
- Add user lifecycle administration across HTTP, gRPC, management
runtime, and SyncTV CLI, including deleted-user listing and restore
operations.
- Add lifecycle migration, cleanup orchestration, audit fields,
resource-change events, and refreshed SQLx offline metadata.
## Design
- Model account, administrator, system, room, and user deletion sources
explicitly so cascade ownership and audit history remain deterministic.
- Release email and OAuth identity occupancy during deletion;
restoration reclaims available identities and reports conflicts
according to restore options.
- Preserve recoverable aggregate rows during the retention window,
remove ephemeral membership and playback state immediately, and purge
expired data in dependency order.
- Wait for realtime observation acknowledgements before publishing
lifecycle changes to avoid subscription registration races.
## Verification
- `make nextest`: 6707 passed, 0 skipped
- `make clippy`: passed for the workspace and all targets
- `make clippy-check`: passed
- `cargo fmt --all -- --check`: passed
- `git diff --check`: passed
## Summary
- Allocate an explicit TAR output buffer sized to the written bytes plus
two 512-byte end blocks.
- Copy the archive payload into the buffer so TAR downloads always
include the required zero blocks.
## Testing
- `npm run validate` from `docs/`
- TAR end-block assertion with `tar-js`
## Summary
- Fix Compose environment archive filenames for issue #385.
- Provide a lazy-loaded TAR download containing all three environment
files.
- Use Starlight code blocks for environment previews and built-in copy
controls.
- Document Linux hidden files and the `ls -a` command after extraction.
## Testing
- `npm run validate` (from `docs/)