Proto commits in JonLatane/jonline

These commits are when the Protocol Buffers files have changed: (only the last 100 relevant commits are shown)

Commit:e0984fb
Author:Jon Latané
Committer:GitHub

Infinite scroll on the Posts, Home and profile feeds (#172)

The documentation is generated from this commit.

Commit:727bbde
Author:Jon Latané
Committer:GitHub

HLS streaming, a full-featured video player, and a miniplayer sharing one queue with the audio bar (#168) * Add HLS streaming for Rellm videos and bottom playback controls for all videos Backend: /media/hls/{id}/master.m3u8, /{variant}/playlist.m3u8 and /{n}.ts serve the stored H.264 MP4 renditions as HLS, segmented on first request by a stream-copy ffmpeg pass into the local temp dir. Same visibility, licensing and CORS handling as /media/{id}, via a shared access check. Elm: every video (Rellm, Bluesky, Mastodon) is wrapped in <rellm-video>, a custom element drawing play/seek/time/mute/fullscreen controls in a shadow root instead of the browser's native controls. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Make HLS streaming an admin setting (MediaSettings.hls_streaming_enabled), off by default HLS segments are made on demand in the server process and cached on local disk per replica, so unlike the stored size conversions it has a runtime cost. Adds the setting (proto field 22, regenerated Elm/Tamagui/Flutter bindings and docs), a switch in the Elm Settings tab, and makes every /media/hls URL a 404 until it's on. Also caches ffprobe results so a master playlist no longer re-fetches and re-probes every rendition per request. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Address review: version HLS URLs, fix segmenting lock handoff, cheaper master playlist, reveal-tap - Playlist/segment URLs carry ?v=<rendition version>; a mismatch is a 404, so a playlist from before a rendition was regenerated can't get the new rendition's segments. - A segmenting lock is only dropped when no other request holds it. - The master playlist downloads and probes only the smallest rendition (for the duration); the others' resolution/bit rate are estimated from their stored aspect ratio and size. - A tap that reveals hidden controls no longer also pauses (the pointerdown that wakes them ran before the click checked). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Give MediaSettings.hls_streaming_enabled a serde default so saved settings still load Without it, a server's already-saved media_settings JSON (which lacks the new key) failed to deserialize, and normalized_media_settings silently replaced the whole blob with defaults -- resetting visibility, moderation, allocation limits and CORS settings. Adds a test that loads a blob saved before the field existed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Compact video controls: fullscreen top-left, mute top-right In the compact (thumbnail-size) form the bottom bar keeps play and seek, and fullscreen and mute move to round buttons in the top corners. Very narrow frames (< 120px, e.g. a portrait video at thumbnail height) get smaller corner buttons and drop the seek bar so nothing overflows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Full-featured video player: volume slider, speed, enlarge/restore, shortcuts, PiP and more Moves the player into public/rellm-video.js (served at the root via a new elm_web route like markdown.js) and adds: a volume button that opens a slider, playback speed/loop/HLS-quality menu at the bottom-right, an enlarge button (thumbnail -> Natural size) with a restore button, buffered-range and hover time on the seek bar, spinner, error message, big play/replay button, keyboard shortcuts, double-click/tap seek and fullscreen, picture-in-picture, Media Session controls, and remembered volume/speed. Elm tells the player its MediaSize via data-media-size. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Miniplayer and one shared queue for audio and video, with handoff from post cards Adds Shared.VideoPlayerPanel (a floating miniplayer) and makes the audio bar's queue the app's one queue: an entry is a track (the bar plays it) or a video (the miniplayer plays it). Starting anything makes it current and puts the existing queue after it (from a list, the rest of that list first); Next/Prev and the end of an entry move through the queue and hand playback between the bar and the miniplayer as the kinds change. The miniplayer has previous/next and a queue button; the bar's controls drive its video. A video or post-card audio that is playing when its page is navigated away from (or whose button / the i key is pressed) carries on in the miniplayer / bar at the same moment. New public/rellm-media.js owns navigation tracking, the audio handoff and 'one thing plays at a time'; rellm-video.js emits the miniplayer's state to the queue. Also waits for hls.js's MEDIA_ATTACHED before resuming a stream. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Address review: one-at-a-time rule, stale handoffs, click-to-play videos - rellm-media.js: an audio-bar slot only skips the bar's *other slot* (not a post card's audio), and a playing video becoming audible (unmuted) pauses the rest. 'Audible' now also means non-zero volume. - A handoff's fetch reply is dropped if the user has since started something else or closed the player (AudioPlayerPanel.selectionGeneration), and a pending seek is cleared when a different track begins. - A video someone pressed play on (it autoplays muted) is marked data-user-started, and unmuting counts too, so it follows them to the miniplayer when they leave. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * CI: build the macOS binary on macos-26 (pinned, not macos-latest) The macos-14 job was being cancelled at 0s; macos-26 is the newest Apple Silicon image, and a fixed label won't change underneath us the way macos-latest does. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * One video player that moves: viewer + expanded player stages, enlarge opens the viewer, no system PiP - The video player panel is a single element that rellm-video.js eases (every frame) between the window corner and a [data-video-stage] placeholder: the expanded player's (the queue pane opening beside it makes the video narrow and slide) and the media viewer's. Videos opened in the media viewer play there, as the queue's current entry; a thumbnail's enlarge button opens the viewer and carries on from the same moment. - Expanded player: a video entry shows just the stage and title (no duplicate scrubber, times or cover art). - Corner chrome: previous/next/queue are the audio bar's own buttons. - Removed the system Picture-in-Picture button (and p key / media-session action); in compact (extra-small/small) videos the miniplayer button replaces the speed button. Removed the in-place enlarge/restore. - A post's own page (Rellm and Bluesky) shows all media at Natural size. - Video panel z-index 29 (over the nav, e.g. the Side Navigation theme), above the viewer only while covering its stage. - The audio bar's iOS unlock clip no longer counts as audible (it paused videos on the first tap). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Address review: editor scrub target, repeated enlarge, swipes over the viewer's video - The viewer's preview-frame/range editor scrubs the shared player's video (it isn't inside .media-viewer-panel-media any more). - Enlarging a thumbnail whose video the panel's player already has now seeks and plays/pauses that player (a rellm-enlarge event) instead of only working for a freshly built element. - A swipe that starts on the player over the viewer's stage presses the viewer's next/prev/close buttons, so touch paging and swipe-to-close still work. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Keep the enlarge handoff for the player that survives the viewer opening The panel's existing player applied the handoff and cleared it, then Elm replaced it (its key includes where it started), so the replacement started from zero. The existing player now only peeks; whoever is built fresh takes it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Drop the enlarge handoff once a surviving player has applied it A player that survives the viewer opening only peeked at the handoff, which then lingered for 5s and could override a queue selection of the same video made right after. It is now cleared shortly after, once any replacement has had its chance. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Miniplayer: no back/close buttons, open-in-viewer button beside fullscreen - Removed the miniplayer's back and close buttons (they overlapped the player's own fullscreen and volume buttons); the expanded player, reached from the queue button, has the close. - The miniplayer's player gets the same 'open in the media viewer' button as thumbnails, right of fullscreen. It opens the viewer on the video (as a URL-only stand-in media) and the same player glides over the viewer's stage, carrying on from where it is. Hidden once on a stage. - Queue buttons in the corner are a bit smaller so they clear the corner buttons. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Side Navigation theme: keep the tab column above the audio/video player The column hangs from the nav bar (z-index 28), above the player (27), so it ran over the expanded player. It now stops where the player starts (and scrolls), animating with the player. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Download/Share for videos, with #video-<mediaId> links like #track- - Rellm-hosted videos in the expanded player get the same Download/Share popover as tracks: a #video-<mediaId>[@server] link (copyable) and a download per stored size (small/medium/large/original). - The address bar follows the current entry: #track- for a track, #video- for a video on one of our servers (nothing for a URL-only Bluesky/Mastodon video); opening a #video- link fetches the media and loads it paused in the expanded player. - Videos carry their server and download sizes through the miniplayer handoff, the media viewer and the queue (VideoItem.host / downloads). - The video panel is cut around an open share popover (it lives in the audio panel, below the video's stage). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Viewer button in the expanded player, shrink-to-miniplayer in the viewer - The expanded player's video gets the 'open in the media viewer' button just right of fullscreen (end of the bar; beside the corner fullscreen button when compact). - In the media viewer the miniplayer button (just left of fullscreen) becomes 'shrink to miniplayer': it closes the viewer and collapses the expanded player, and the same player glides to the corner, still playing. The viewer button is hidden there. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Address review: stale #video- replies, links waiting on a server, enlarge keeps Rellm video data - A #video-/#track- reply carries the player's selectionGeneration from when it was requested and is dropped if the user has started something else or closed the player since. - A link still waiting for its server is dropped when something else starts or the player closes. - Enlarging a Rellm video hands the viewer a bare URL; the queue's entry for the same video now keeps its server and download sizes (so Download/Share and its #video- link survive). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J * Offer to add the server a #track-/#video- link names A shared link to a track or video on a server the recipient hasn't added (or has disabled) now shows the established Add / Enable button (logo and name from the anonymous preview, only for public-looking hostnames, as for quoted links). Nothing connects until it is pressed; then the pending link opens by itself. The prompt can be dismissed, which forgets the link. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J --------- Co-authored-by: Claude <noreply@anthropic.com>

Commit:e6434fe
Author:Claude

Make HLS streaming an admin setting (MediaSettings.hls_streaming_enabled), off by default HLS segments are made on demand in the server process and cached on local disk per replica, so unlike the stored size conversions it has a runtime cost. Adds the setting (proto field 22, regenerated Elm/Tamagui/Flutter bindings and docs), a switch in the Elm Settings tab, and makes every /media/hls URL a 404 until it's on. Also caches ffprobe results so a master playlist no longer re-fetches and re-probes every rendition per request. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvFSL9uw7s6qNjzmVbfi7J

Commit:2c5a007
Author:Jon Latané
Committer:GitHub

Make the home page's embedded Posts feed follow the default feed too (#167)

Commit:56b8fef
Author:Jon Latané
Committer:GitHub

Configurable default Posts feed (server setting + user preference) (#164)

Commit:2fc4485
Author:Jon Latané
Committer:GitHub

Countabilibar: per-server/per-user post and event counts with hide/show (#144)

Commit:37054fd
Author:Jon Latané
Committer:GitHub

StellarWP (The Events Calendar) sync sources: fetch every page of an iCal export (#142)

Commit:ef07ae5
Author:Jon Latané
Committer:GitHub

Bluesky AppView logos (built-in bsky.app logo + admin logo_url) (#141) * Bluesky AppView logos: built-in butterfly for bsky.app, admin-set BlueskyServer.logo_url An AppView has no way to describe itself (unlike a Mastodon instance's thumbnail or a Rellm server's ServerLogo), so the logo is either bundled (the butterfly, as an inline SVG, for public.api.bsky.app) or set by an admin via the new optional BlueskyServer.logo_url, which overrides it. Shown on the AppView chip and the recommended-server chip; the Federation tab's Bluesky Servers editor gets a logo URL box with a live preview. Only http(s) URLs count. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Logo URLs: https only, matching BlueskyServer.logo_url's documented contract Greptile flagged that the proto/editor said https but normalizeLogoUrl also accepted http. http images are blocked as mixed content on https deployments anyway. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:8f998d0
Author:Jon Latané
Committer:GitHub

Recently Popular Posts: RECENTLY_POPULAR_POSTS listing + sort select (#138) * Bluesky: support text search when browsing an AppView anonymously public.api.bsky.app 403s app.bsky.feed.searchPosts, but api.bsky.app (the same AppView's other front door) serves it with no login and CORS enabled. An anonymous BlueskyAppViewFeed with a non-blank search now sends searchPosts there (Common.anonymousSearchHost) instead of ignoring the search, and a changed search refetches AppView sources like it does connected accounts. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Bluesky: newest-first default for anonymous and logged-in sources Anonymous AppView feed: replace the hours-old What's Hot generator with searchPosts q=* sort=latest (newest on the network, seconds behind the firehose), on api.bsky.app. Logged-in home timeline: request algorithm=reverse-chronological explicitly, and searchPosts sort=latest explicitly, rather than relying on server defaults. Mastodon's local public timeline is already newest-first, and PostsPage sorts the merged list by recency when there's no search text. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * PostsPage: drop stale feed responses so an older search can't overwrite a newer one Each fetch refetchFeeds starts is tagged with a model-wide monotonic requestId stored on the source's ServerFeed; GotFeedPosts only applies a response (loaded or failed) whose id is still current. Previously a slow response for an earlier search could land after a newer search (or after clearing the search) and replace its results. Token-refresh side effects from a stale response are still applied. Addresses Greptile's review on #136. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * RECENTLY_POPULAR_POSTS listing + Recent/Recently Popular Posts select Proto/backend: new PostListingType RECENTLY_POPULAR_POSTS = 6. GetPosts ranks the last 14 days of visible top-level posts (optionally author-scoped, and as of published_or_created_before) by (stars + 2*responses + 1) / (age_hours + 2)^1.5, ties to recency. Adds partial covering index idx_posts_recently_popular. Tests. Elm: the "Recent Posts" heading/tab on the standalone page, Pages.Home_ and UserProfilePage's embedded copy is now a select (Recent Posts / Recently Popular Posts, default Recent), persisted as ?sort=popular. Popular maps to RECENTLY_POPULAR_POSTS on Rellm servers, Mastodon trends/statuses, and Bluesky's What's Hot feed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * PostsPage: keep each source's rank in Recently Popular Posts mode postsListView always re-sorted by recency when search was blank, so ranked results (RECENTLY_POPULAR_POSTS, Mastodon trends, Bluesky What's Hot) lost their order. PostAnimation now carries each post's fractional position within its own source's response (rank); in popular mode the list sorts by rank (sources interleave: every source's top post, then every second, ...), recency only breaking ties. The Posts Before tab and text search behave as before. Addresses Greptile's review on #138. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Posts Before: newest-first (or most relevant, with search) before the date, on every platform - Popular sort only applies on the Recent tab, for Mastodon/Bluesky too (they flip with the tab, and tab/date changes refetch them). - Mastodon public timeline and Bluesky (AppView and account) now honor the Posts Before cutoff: Mastodon via a snowflake max_id, Bluesky search via until (q=a for the blank-search AppView listing, since q=* can't take until), and the account home timeline by filtering a larger page. - Text search is displayed by relevance (rank), not dictionary-key order; Bluesky text search asks for sort=top; Mastodon is hidden while searching since its timeline can't be searched anonymously. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:4a4dcfe
Author:Jon Latané
Committer:GitHub

Bluesky: browse bsky.app anonymously as a server; log in at an account's own PDS (#135)

Commit:0fc5b06
Author:Jon Latane

Add BlueskyServer (FederationInfo.bluesky_servers) proto + backend plumbing Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:2063dc0
Author:Jon Latané
Committer:GitHub

EventPage: owner-editable RSVP settings (#128) * EventPage: owner-editable RSVP settings; persist Occasion RSVP overrides Above the RSVP area, the Event's author (or an Admin) gets an Allow RSVPs switch. The RSVP area and an expandable "RSVP Settings" section (anonymous RSVPs, attendee limit, per-occasion attendee-limit override, hide location until approved, default RSVP moderation) animate in/out with it. Every control saves immediately: fetch a fresh Event, patch the one field, then UpdateEventDetails / UpdateOccasions. Backend: Occasion.info (RSVP overrides) was neither stored by UpdateOccasions/CreateNewOccasions/CreateEvent nor returned by the marshaler; both now round-trip it (counts are never stored). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Enforce attendee limits in upsert_rsvp EventInfo.max_attendees (overridden per Occasion by OccasionRsvpInfo.max_attendees) now limits GOING RSVPs with passing moderation, counted by number_of_guests. Interested/pending/rejected RSVPs never use capacity; approving a pending GOING RSVP or growing an existing one is checked too, while unrelated edits to an RSVP that's already in are not. upsert_rsvp now runs in a transaction holding a row lock on the Occasion so concurrent RSVPs can't race past the limit. Fails with FailedPrecondition "attendee_limit_reached", shown in the Elm UI as a readable message. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Address review: serialize RSVP-setting saves, enforce allow/anonymous/default-moderation Elm: - RSVP-setting edits now run one at a time (queued), each against a freshly fetched Event, so quick edits can't overwrite each other with stale copies. A failure drops the queue and refetches instead of restoring a snapshot. - The Allow RSVPs / Allow anonymous switches patch the Occasion's own override when it has one (Rsvps.rsvpsAllowed prefers it), so they can't bounce back. - A max-attendees limit of 0 is no longer accepted; the typed-but-uncommitted input is cleared when a limit is toggled. Backend (upsert_rsvp): - New RSVPs are refused unless allows_rsvps (and allows_anonymous_rsvps for anonymous ones) is on for the Occasion/Event; existing RSVPs can still change. - EventInfo.default_rsvp_moderation applies to logged-in non-owner RSVPs. - Unreadable event/occasion info is logged instead of silently ignored. Protos: document the enforced semantics (docs and bindings regenerated). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:a1a6d51
Author:Jon Latane

Address review: serialize RSVP-setting saves, enforce allow/anonymous/default-moderation Elm: - RSVP-setting edits now run one at a time (queued), each against a freshly fetched Event, so quick edits can't overwrite each other with stale copies. A failure drops the queue and refetches instead of restoring a snapshot. - The Allow RSVPs / Allow anonymous switches patch the Occasion's own override when it has one (Rsvps.rsvpsAllowed prefers it), so they can't bounce back. - A max-attendees limit of 0 is no longer accepted; the typed-but-uncommitted input is cleared when a limit is toggled. Backend (upsert_rsvp): - New RSVPs are refused unless allows_rsvps (and allows_anonymous_rsvps for anonymous ones) is on for the Occasion/Event; existing RSVPs can still change. - EventInfo.default_rsvp_moderation applies to logged-in non-owner RSVPs. - Unreadable event/occasion info is logged instead of silently ignored. Protos: document the enforced semantics (docs and bindings regenerated). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:69765e8
Author:Jon Latané
Committer:GitHub

Embed quoted posts and social media links; remove embed_link (#127) * Show quoted posts on Bluesky/Mastodon post cards and pages Add Post.quoted_post (client-populated) so federation translators can embed a quoted post. Bluesky: decode app.bsky.embed.record/recordWithMedia (cards show one quote deep; BlueskyPostPage resolves up to 5 by fetching each further getPosts). Mastodon: same for 4.5+ quote.quoted_status/quoted_status_id. Rendered as an embedded, clickable card (Posts.quotedPostCard). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Add quoted_post_id to Rellm posts and a quote button on the post page - posts.quoted_post_id column (FK, ON DELETE SET NULL; also cleared on soft-delete) - CreatePost/UpdatePost accept Post.quoted_post_id: POST context only, quoted post must be visible to the author, no self-quotes; reads hydrate quoted_post (one level, viewer-visibility checked) - Elm: quote icon button on the post detail view opens CreateNewPanel with a populated 'quoted post' input; quoted posts render on cards and the detail view - Regenerated Elm/Tamagui bindings and protocol docs Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * CreateNewPanel: show the quoted post as a postCard with an X to remove it The quote is kept when switching to Event mode (hidden there, never submitted). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Shorten Bluesky post links to /post/did:plc:...~rkey@bluesky: Drop the at:// scheme and collection from the route id (did~rkey) and the connected account's handle from the host (bare bluesky:). Old long links still parse. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Regenerate Flutter protos for Post.quoted_post/quoted_post_id Generated with protoc_plugin 21.1.2 on Dart 3.4.1 (the Flutter 3.22.1-pinned toolchain). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Make FVM the obvious Flutter toolchain - Flutter Makefile: fvm_check/fvm_ready/fvm_install; every target depends on fvm_ready and fails with setup instructions if FVM or the pinned SDK is missing - Flutter README: 'Toolchain: use FVM' section, accurate proto generation notes - CLAUDE.md and rebuild-protos skill: say to use fvm, and that Flutter bindings are committed Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Show Bluesky/Mastodon account avatars in the Accounts Panel button The toggle only considered Rellm accounts, so signing in with only a Bluesky or Mastodon account still showed "Login". Now every signed-in account (enabled Rellm, enabled Bluesky, connected Mastodon) gets an avatar. Mastodon accounts now store their avatar (from verify_credentials, refreshed by the startup health check); a Bluesky account missing one fetches its profile at startup. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Quote Rellm posts via the existing link column, resolved client-side Drop quoted_post_id and its migration. A POST whose link points at a post/event on any Rellm server (/post/id, /event/id, /~id, relative or absolute) is embedded; servers are resolved through AccountsPanel previews, with a nonRellmHosts negative cache. CreateNewPanel quotes now write the link. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Embed quoted events as event cards A quote link is still fetched with GetPosts; if the post is an EVENT or OCCASION, follow up with GetEvents and render Components.Events.eventCard (read-only) instead of the plain quoted-post card. Falls back to the post card if the event can't be fetched. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Redirect /post/:id of an event or occasion to its EventPage Pages.Post.PostId_ replaces the URL with /event/:id once the loaded post's context is EVENT or OCCASION (not in PostPage itself, which Home and custom tabs embed under URLs that must stay put). EventPage accepts the event's own post id too, showing its first occasion. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Docs: event embeds and the quote-link design Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Embed Bluesky and Mastodon posts linked from a Rellm post Shared.Federation.FederatedLinks recognizes bsky.app / at:// links, Mastodon status URLs on any instance, and Rellm's own /post/id@bluesky: and @mastodon:host routes (on any server). QuoteLinks fetches Bluesky anonymously from the public AppView (resolving handles) and Mastodon from the linked instance (public domains only); QuotedLink renders the post card. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Remove Post.embed_link: supported links are always embedded Drops the proto field (reserved 12) and the posts.embed_link column (new migration, reversible), plus every backend use. The React app no longer has the "Embed Link" toggle or per-post state; PostMediaRenderer embeds any link to a supported site, and guards against unparseable URLs now that it always tries. Generated bindings/docs regenerated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Elm port of react-social-media-embed (SocialMediaEmbed) Facebook, Instagram, LinkedIn, Pinterest, TikTok, X and YouTube links on a Rellm POST always embed, as sandboxed lazy iframes on each network's own embed page (no third-party scripts), with the library's placeholder link/spinner under them. Each network is its own module with a pure fromLink; index.html resizes iframes from Instagram/X resize messages. Wired into QuotedLink's quote slot. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Regenerate diesel/schema.patch so it applies to current print-schema output Hunk #2 (ai_provider_grants.model_names) no longer applied: print-schema from a database built by the migrations already emits the column in its final position, so only its type needs patching. Regenerated the whole patch from raw print-schema output vs src/schema.rs (verified: diesel migration run applies it cleanly and reproduces src/schema.rs byte for byte, incl. embed_link removal). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR * Add make typecheck for the Tamagui app (and install_deps without the postinstall build) yarn install --mode=skip-build + yarn tsc --noEmit type-checks the React app in ~15s, the same tsc pass make build starts with. Documented in CLAUDE.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR --------- Co-authored-by: Claude <noreply@anthropic.com>

Commit:5178a6c
Author:Claude

Remove Post.embed_link: supported links are always embedded Drops the proto field (reserved 12) and the posts.embed_link column (new migration, reversible), plus every backend use. The React app no longer has the "Embed Link" toggle or per-post state; PostMediaRenderer embeds any link to a supported site, and guards against unparseable URLs now that it always tries. Generated bindings/docs regenerated. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR

Commit:7b8813a
Author:Claude

Embed Bluesky and Mastodon posts linked from a Rellm post Shared.Federation.FederatedLinks recognizes bsky.app / at:// links, Mastodon status URLs on any instance, and Rellm's own /post/id@bluesky: and @mastodon:host routes (on any server). QuoteLinks fetches Bluesky anonymously from the public AppView (resolving handles) and Mastodon from the linked instance (public domains only); QuotedLink renders the post card. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR

Commit:8162aed
Author:Claude

Quote Rellm posts via the existing link column, resolved client-side Drop quoted_post_id and its migration. A POST whose link points at a post/event on any Rellm server (/post/id, /event/id, /~id, relative or absolute) is embedded; servers are resolved through AccountsPanel previews, with a nonRellmHosts negative cache. CreateNewPanel quotes now write the link. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR

Commit:51a190a
Author:Claude

Add quoted_post_id to Rellm posts and a quote button on the post page - posts.quoted_post_id column (FK, ON DELETE SET NULL; also cleared on soft-delete) - CreatePost/UpdatePost accept Post.quoted_post_id: POST context only, quoted post must be visible to the author, no self-quotes; reads hydrate quoted_post (one level, viewer-visibility checked) - Elm: quote icon button on the post detail view opens CreateNewPanel with a populated 'quoted post' input; quoted posts render on cards and the detail view - Regenerated Elm/Tamagui bindings and protocol docs Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR

Commit:fd1b081
Author:Claude

Show quoted posts on Bluesky/Mastodon post cards and pages Add Post.quoted_post (client-populated) so federation translators can embed a quoted post. Bluesky: decode app.bsky.embed.record/recordWithMedia (cards show one quote deep; BlueskyPostPage resolves up to 5 by fetching each further getPosts). Mastodon: same for 4.5+ quote.quoted_status/quoted_status_id. Rendered as an embedded, clickable card (Posts.quotedPostCard). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoRfmxaTeniMYdwmmqUyiR

Commit:7ddda04
Author:Jon Latané
Committer:GitHub

Custom CSS for servers: stylesheet, forced theme, and 35 styles, 105 templates in total (#125) * Initial Custom CSS support * initial style templates * force dark/light mode with custom CSS * primary/nav colors * user demos of template system * more stuff * Use plain glyphs, not emoji, for arrows/triangles on iOS iOS draws symbols that have an emoji form (↗ ◀ ▶, ...) as color emoji -- the blue-square reorder buttons, the ↗️ external-link buttons and the Bluesky "Create an App Password" link. Add UI.Glyph, which appends the text-presentation selector (U+FE0E) once, and use it for every ↗/◀/▶ control: external links, the Flip reorder buttons, play buttons and the post collapse toggle. Deliberate emoji icons are untouched. Documented in CLAUDE.md. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Faster server port detection: local-host aware, with a remembered connection Detection ran strictly in sequence with 5s timeouts, and a dev server was its worst case (two wrong TLS guesses plus a CDN lookup before reaching plaintext :27707). Now: - local / LAN hosts (localhost, *.local, private IPs, single-label names) try plaintext first, skip the CDN backend_host lookup, and use a 1.5s per-attempt timeout; - each server's last working connection is persisted and tried first on the next visit, falling back to full detection if it is stale (older saved state still loads). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Fix /elm -> / redirect mangling the path on cold visits rootRedirectCmd stripped four characters off an already-normalised path (as if it still began with /elm), so /elm/about became /ut on a first visit to a server whose default UI is Elm. Redirect to the normalised path as-is; the /auth/ and oauth-callback exemptions are now checked against that same path so they can match. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Custom CSS editor: arrows + dropdown never wrap, Preview right-aligned The template picker is now one unwrappable row (text arrows <- / ->, which iOS does not emoji-ify, around a shrinking dropdown); the Preview button may wrap below it and stays right-aligned. Also drops the Undo template button. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Hide Mastodon/Bluesky posts with sensitive media from feeds by default Federated posts whose media was stripped as sensitive (Mastodon's sensitive flag with attachments; any Bluesky content label) are now dropped as feeds load, alongside the existing custom-nav filter in PostsPage.GotFeedPosts. A new Accounts Panel Debug switch, "Show Mastodon/Bluesky posts with sensitive media", brings them back with their existing click-through "sensitive media" notice; flipping it refetches the feeds so the posts animate in/out. The filter is a pure function (filterFeedPosts) with tests. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Post cards: click-through media wrapper; keep media strips scrollable; defer sign-in form until Accounts Panel opens - Single-media card wrapper is click-through (only the media itself is interactive), so blank space beside a thumbnail opens the post. Timestamps are click-through on touch devices. - Horizontal media strips hug their items instead of going pointer-events:none, since iOS WebKit won't touch-scroll a pointer-events:none scroller. - The Accounts Panel's username/password inputs are only mounted once the panel has been opened, so password managers (1Password) don't prompt on page load. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Don't clip the top edge of hover-lifted cards The flip clip wrapper (overflow:hidden, flush with the card's top) sheared the top border/shadow off cards that lift on hover (translateY(-1px) in Standard Style, translate(-2px,-2px) in Bauhaus, etc.). Give the wrapper 6px of headroom with an equal negative margin so layout is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Regenerate proto bindings and protocol docs for CustomCSSConfiguration Fixes the Proto/Docs Consistency check: the --primary-color/--nav-color doc comment change hadn't been regenerated into the Elm/Flutter/Tamagui bindings, and docs/protocol.* were never regenerated for the Custom CSS RPCs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Custom CSS review: lock-and-carry-forward config versions in-transaction, weak/list If-None-Match, lifecycle specs - ConfigureServer and ConfigureCustomCSS now read the active row's carry-forward columns inside their transaction under FOR UPDATE, so interleaved saves can't revert each other. - /custom_css.css honors weak (W/) and list-valued If-None-Match, as sent after a CDN rewrites the ETag. - Specs: versioning/history, rejected saves create no version, ConfigureServer and ConfigureCustomCSS preserve each other's settings, legacy and unparseable stored JSON. - Fix misindented field in From<ServerConfiguration>. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Federation tab: load preview branding for federated servers that aren't added The Accounts Panel's recommended-servers strip already fetches name/colors/logo for servers the user hasn't added (recommendedServerConnections). Factor that fetch into AccountsPanel.fetchServerPreviews, expose it as an idempotent EnsureServerPreviews message, and have FederationTab request it for every saved and pending federated host and read the same cache in federatedServerFor. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Enable/Add <server> buttons show the server's logo and name via the shared preview loader - RellmServers.previewOf: the user's connected server, else its preview from the Accounts Panel's preview cache (the recommended-servers loader), else a bare host placeholder. FederationTab's federatedServerFor now uses it. - ServerDependentView (Post/Event/Profile pages) and StarredPanel: Enable/Add buttons render 'Enable|Add <logo + 1-2 line name>' (ServerDependentView.hostLabel, the horizontal logo used by the nav's Home button) instead of the bare hostname. - The three ServerDependentView pages request the host's preview on init (EnsureServerPreviews; a no-op for already-connected servers). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Apply a server's forced theme before first paint /custom_css.css now announces a forced theme as '--forced-theme: light|dark' on :root (light wins if both, as in the Elm client). A small script in index.html, held by the parser until that stylesheet has loaded, sets <html data-theme> from it, so a returning visitor no longer sees their own theme until the Elm app boots and reaches the server's configuration. Mirrored in UI.CustomCssStylesheet; specs on both sides. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Add Standard Side Navigation and ten more Custom CSS styles Standard Side Navigation: Standard Style plus a pure-CSS left nav column on wide screens (tab styles honored, Accounts top-right, toggles beside Home). New styles: Psychedelic Poster, Haunted Mansion, Pirate Map, Mac Classic, OS X (Original), Material Design, Liquid Glass, Candy Shop, Comic Book, Deep Space -- each in 0/1/2-image versions (105 templates total), placed in the dropdown order after related styles. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Side nav: move the whole page area right of the column, re-derive breakout margins The events grid/strip/calendar, messages page and media gallery break out of the 800px column by 1vw-from-the-edge margins and ran underneath the side column. In Side Navigation, the column is now placed with --side-left and those margins are re-derived from it; the breakpoint drops to 1000px. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Side nav: vertical page transitions Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:f23ff35
Author:Jon Latane

primary/nav colors

Commit:864a649
Author:Jon Latane

force dark/light mode with custom CSS

Commit:363a638
Author:Jon Latane

Initial Custom CSS support

Commit:b810289
Author:Jon Latané
Committer:GitHub

Rework deployment and add audio updates (#123) * Rework deployment and add audio updates * Address review: media conversion races/leaks, download names, cache, deploy guards Media: - retag_media only commits if the row is unchanged since it was read, so an UpdateMedia during a retag is no longer overwritten (the retag re-runs). - Uploads from a conversion/retag that doesn't commit are deleted (the versioned paths would otherwise leak on every retry). - retag maps only v/a/s streams (no data streams) and falls back to a full reconversion if it fails before committing, instead of failing forever. - ?download=true names the copy actually served (e.g. "(preview)" for unlicensed viewers) and strips path separators from the name. - The web server's on-disk media cache evicts superseded versions of an object when a new version is cached. Deploys: - remove_ingress/deploy_ingress_controller_delete, remove_email and the delete_central_* targets need CONFIRM=<namespace> like the other destructive targets. - New view_logs target (every pod in the namespace, all containers) with --tail/--lines like the other view_*_logs targets. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * deploys tests: don't let an outer make's environment leak into test makes Under `make -C deploys test` (as CI runs it) the tests inherited MAKELEVEL/MAKEFLAGS, so GNU make 4+ printed "Entering directory" lines even with -s, and make_var returned them as if they were targets (guard_tests failed). Unset the inherited vars and pass --no-print-directory in make_var. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:730dcb2
Author:Jon Latane

Rework deployment and add audio updates

Commit:612e81c
Author:Jon Latane

BPM + key analysis, minor fixes

Commit:5f55523
Author:Jon Latane

more preview generation tweaks, dev access tweaks

Commit:3839ff1
Author:Jon Latane

AudioPlayerPanel refinements

Commit:5d4e8b0
Author:Jon Latane

various updates for audio media

Commit:9c472b0
Author:Jon Latane

more options around link previews

Commit:6f96825
Author:Jon Latane

various UX

Commit:37d057e
Author:Jon Latané
Committer:GitHub

Completeness Pass (#122) * stuff * last bits * EventAttendance -> Rsvp * RSVPs in Elm and some Tamagui tsc speedups * more fixes * fully federated profiles * deeper Mastodon/Bsky integrations * last bits of cleanup * Review fixes: block SSRF in link-preview image fetch, log RSVP count query errors, fix ical formatting Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Document new /calendar.ics params (post_id, anonymous_auth_token) in rellm.proto Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Document unsecure-localhost federated sign-in developer option in rellm.proto Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * rebuild protos * Remove backdrop blur on moving StarredPanel entries (perf) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Revert "Remove backdrop blur on moving StarredPanel entries (perf)" This reverts commit 2ad92c6cc4f7167dadabd798a916ba0f3d53696a. * fix loop --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:56726bb
Author:Jon Latane

Document unsecure-localhost federated sign-in developer option in rellm.proto Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:35dd5a2
Author:Jon Latane

Document new /calendar.ics params (post_id, anonymous_auth_token) in rellm.proto Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Commit:10238f9
Author:Jon Latane

last bits of cleanup

Commit:75d0a60
Author:Jon Latane

more fixes

Commit:03142c5
Author:Jon Latane

RSVPs in Elm and some Tamagui tsc speedups

Commit:7efb1cb
Author:Jon Latane

EventAttendance -> Rsvp

Commit:d8638e5
Author:Jon Latane

stuff

Commit:48666c3
Author:Jon Latané
Committer:GitHub

Media Overhaul (#121) * contact UX refinements * multi-push * media panel UX tweaks * audio preview thumbnails * media overhaul * ImagesPage * refinements

Commit:5b34ed1
Author:Jon Latane

refinements

Commit:b1b4aba
Author:Jon Latane

ImagesPage

Commit:8250e06
Author:Jon Latane

media overhaul

Commit:67ad327
Author:Jon Latane

audio preview thumbnails

Commit:eb25643
Author:Jon Latané
Committer:GitHub

Server media limits (#118) * server-enforced object storage usage limits * UI tweaks

Commit:723d9f8
Author:Jon Latane

server-enforced object storage usage limits

Commit:779ab6a
Author:Jon Latané
Committer:GitHub

Replace MinIO with Silo, rename whole service category to object_storage (#117)

Commit:fe20385
Author:Jon Latane

Replace MinIO with Silo, rename whole service category to object_storage

Commit:a4c9501
Author:Jon Latane

simplify some stuff

Commit:b988048
Author:Jon Latané
Committer:GitHub

Contact Method Consent, Telnyx SMS integration support (#116) * Telnyx support * Contact Consent and more * elm-review

Commit:cc428ba
Author:Jon Latane

Contact Consent and more

Commit:2452acb
Author:Jon Latane

Telnyx support

Commit:dcfe6f7
Author:Jon Latané
Committer:GitHub

More Market tweaks and Mastodon/Bluesky integration improvements (#115) * More Market tweaks and Mastodon/Bluesky integration improvements * fix tests

Commit:fca94b5
Author:Jon Latane

More Market tweaks and Mastodon/Bluesky integration improvements

Commit:70bd3ba
Author:Jon Latané
Committer:GitHub

Expand Market Features (#114)

Commit:d31e865
Author:Jon Latane

all the things

Commit:f2e8202
Author:Jon Latane

greatly expand market stuff

Commit:dd8d877
Author:Jon Latané
Committer:GitHub

Rellm's Market (#113)

Commit:f213741
Author:Jon Latane

Add PermissionsAccess purchase type, card details on payments/refunds, and Market SSR previews - protos/market.proto: PURCHASE_TYPE_PERMISSIONS_ACCESS lets an admin sell arbitrary Permission grants (e.g. pay-gating Facebook sync) as a subscription; MarketPaymentMethod/MarketRefundMethod now carry real card brand/last4/expiry resolved from Stripe at charge time - Backend: fulfillment additively grants the product's configured permissions to the buyer; stripe_sync resolves card details via PaymentIntent/PaymentMethod expansion; new logic::market_summary generates human-readable /market/product/:id SSR previews (og:description) wired into web::spa_pages, with an explanatory note on Rellm Hosting listings about full admin access (e.g. self-service Facebook sync pay-gating) - Elm: admin product form supports Permissions Access (comma-separated permission picker), and the Subscriptions section now shows billing history with card details when available Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:13e4ea8
Author:Jon Latane

Add Rellm's Market: Stripe-backed subscriptions for media storage, AI grants, and Rellm hosting - protos/market.proto: MarketProduct/MarketSubscription/MarketPurchase/MarketPayment/MarketRefund types and 5 new RPCs (GetMarketProducts, CreateMarketProduct, UpdateMarketProduct, GetMarketSubscriptions, MakeMarketPurchase) - Backend: DB schema, Stripe Checkout + webhook-driven fulfillment (never trusts the client-side redirect), single renew_market_subscriptions background job for recurring billing, StripeConfig in ServerConfiguration, and the long-overdue default_media_allocation_bytes wiring for new users - Elm: /market + /market/product/:id pages, IntegrationsTab Stripe editor, a read-only Subscriptions section on UserProfilePage, an AccountsPanel chip entry, and MARKET_TAB custom-nav wiring throughout Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:ea32126
Author:Jon Latane

pre-clauding

Commit:157ae81
Author:Jon Latané
Committer:GitHub

Video thumbnail image generation (#112)

Commit:926660c
Author:Jon Latane

stuff

Commit:96358eb
Author:Jon Latane

nav tab styles

Commit:5029c53
Author:Jon Latané
Committer:GitHub

Media overhaul for quota tracking + future enhancements (#110) * proto changes * Wire up Media storage quotas end-to-end Backs the media-overhaul proto scaffolding (User.media_storage_limit_bytes/ media_storage_bytes_used, Media/MediaReference.sizes) all the way through: - DB: restructure media's minio_path/content_type/converted_sizes/aspect_ratio into one `sizes` JSONB array (one entry per stored copy, original included), plus indexes for the new per-user lookups and the startup backfill query. - Backend: quota enforcement on upload (413 + message when it would exceed User.media_storage_limit_bytes), a denormalized media_storage_bytes_used counter (mirroring the existing follower_count/post_count pattern), and a non-blocking startup task that backfills real byte counts for pre-existing Media via MinIO stats. - New UpdateMedia/DeleteMediaSizes RPCs, with specs. - Elm: quota error surfacing + a used/available storage readout in MyMediaPanel; RellmAccount now tracks the two new User fields. - Tamagui: content-type/aspect-ratio call sites updated for the move from flat Media fields to per-size MediaSize entries. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Admin-settable storage quotas on profiles; MediaViewerPanel editing - UpdateUser: admins can now set/clear any user's media_storage_limit_bytes (self-updates can't touch their own). Adds MediaReference.user_id so MediaViewerPanel can tell who owns a given item. - UserProfilePage: a new "Media Storage" section (collapsed, right above Permissions) lets an admin edit a user's quota (number + KB/MB/GB unit, or Unlimited), and shows the profile owner their own percent-used readout plus a "Show My Media" button opening MyMediaPanel. - MediaViewerPanel: an Edit button (owner-or-admin only) opens a small overlay to rename/redescribe the item and delete individual sizes (original/small/medium/large, each labeled with its friendly byte size), via the new UpdateMedia/DeleteMediaSizes RPCs. Its update now threads AccountsPanel.Model (for the RPC calls) and forwards an optional account-refresh, like MyMediaPanel already does; all 3 call sites in Shared.elm updated accordingly. - New Shared.ByteFormat module (human-readable formatting/parsing for byte counts) shared by MyMediaPanel, UserProfilePage, and MediaViewerPanel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Replace Media/MediaReference.user_id with a full Author authors.proto's Author.avatar already needed MediaReference, so importing authors.proto back into media.proto for Media/MediaReference.author would have been a circular file import (protoc rejects those even though the recursive types themselves are fine). Resolved by moving Author into media.proto itself and deleting authors.proto -- its other importers (ai_providers.proto, sync.proto, messages.proto, posts.proto) now just depend on media.proto instead, which most already did anyway. Media/MediaReference.author is populated with a real Author only at the media-specific RPCs (GetMedia, UpdateMedia, DeleteMediaSizes, GenerateMedia, via a batched author lookup); every other Media/MediaReference marshaling call site (User/Group avatars, Post media, ...) passes None, since the owner is already obvious from context there and this avoids needing an author lookup at every single one. Elm/Rust both represent the Author<->MediaReference recursion with a boxed/ wrapped indirection (prost's `boxed` field option resp. elm-protobuf's wrapper type + wrap/unwrap functions) since neither language allows an unboxed self-referential struct. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Switch Twilio auth from Auth Token to API Keys TwilioConfig previously authenticated with the account's own Auth Token (twilio_api_key, paired with the Account SID) -- a single unscoped, full-access credential with no way to revoke it without rotating everything else that uses it. Switched to a Twilio API Key instead: twilio_api_key_sid/twilio_api_key_secret now authenticate the request (Basic Auth), while twilio_account_sid is kept only for the API's URL path, never for authentication. Twilio's own docs recommend a Restricted API Key scoped to just /twilio/messaging/messages/create for this. Existing TwilioConfigs were stored under the old (now invalid) shape, so a data-only migration clears them -- admins need to re-enter credentials as an API Key pair via the (also newly fixed) Integrations tab. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Format verification SMS with server name and CDN frontend host The SMS body was a bare "Your verification code is {code}", giving a recipient no way to tell which server (of potentially several Rellm instances) is texting them. Now formats as "Phone verification requested from {server_info.name} ({external_cdn_config.frontend_host}). Your code is: {code}. Do not share this code with anyone." -- omitting the parenthetical when no CDN frontend host is configured. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

Commit:eed1abc
Author:Jon Latané
Committer:GitHub

Rename AIModelProvider/AvailableAIModel/EventInstance for clarity (#109) * Rename AIModelProvider->AIProvider, AvailableAIModel->AIModel, EventInstance->Occasion Renames these types throughout the proto contracts, Rust backend (including DB tables/columns via new migrations), and Elm/Tamagui/Flutter frontends: - AIModelProvider -> AIProvider (ai_model_providers.proto -> ai_providers.proto, ai_model_providers/ai_model_provider_grants tables -> ai_providers/ai_provider_grants) - AvailableAIModel -> AIModel (proto/backend only; no DB table) - EventInstance -> Occasion (event_instances table -> occasions, plus event_instance_sync_destinations -> occasion_sync_destinations and all event_instance_id/event_instance_count columns -> occasion_id/occasion_count) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Clean up remaining Occasion-related instance/instances naming Extends the EventInstance->Occasion rename to every remaining Occasion-related use of "instance"/"instances" across protos, the Rust backend, and the Elm/Tamagui/Flutter frontends (the Event.instances proto field is now Event.occasions), while leaving genuinely unrelated uses of "instance" untouched (Mastodon server instances, Rocket/cluster server instances, the SyncDestinationStatus.destination_instance_id field, generic "for instance" prose, etc.). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix Integrations tab not showing saved Twilio/Bird settings twilio_config/bird_config/preferred_verification_apis are admin-only- serialized fields, stripped from the unauthenticated GetServerConfiguration probe RellmServers.configurationOf reflects. IntegrationsTab was reading that probe directly, so it always rendered blank/default settings regardless of what was actually saved -- a race condition already solved for cluster_resources in ClusterTab (its own authenticated GetServerConfiguration fetch, fired from every point connectivity could plausibly have settled). Ported that same pattern to IntegrationsTab. Also adds configure_server tests locking in that Twilio's Auth Token and Bird's Access Key are never returned to the client and are preserved (not clobbered) when a save leaves that field blank. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

Commit:29383da
Author:Jon Latane

Clean up remaining Occasion-related instance/instances naming Extends the EventInstance->Occasion rename to every remaining Occasion-related use of "instance"/"instances" across protos, the Rust backend, and the Elm/Tamagui/Flutter frontends (the Event.instances proto field is now Event.occasions), while leaving genuinely unrelated uses of "instance" untouched (Mastodon server instances, Rocket/cluster server instances, the SyncDestinationStatus.destination_instance_id field, generic "for instance" prose, etc.). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:c7d335c
Author:Jon Latane

Rename AIModelProvider->AIProvider, AvailableAIModel->AIModel, EventInstance->Occasion Renames these types throughout the proto contracts, Rust backend (including DB tables/columns via new migrations), and Elm/Tamagui/Flutter frontends: - AIModelProvider -> AIProvider (ai_model_providers.proto -> ai_providers.proto, ai_model_providers/ai_model_provider_grants tables -> ai_providers/ai_provider_grants) - AvailableAIModel -> AIModel (proto/backend only; no DB table) - EventInstance -> Occasion (event_instances table -> occasions, plus event_instance_sync_destinations -> occasion_sync_destinations and all event_instance_id/event_instance_count columns -> occasion_id/occasion_count) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:49702a0
Author:Jon Latané
Committer:GitHub

RSS/Atom Support (#108)

Commit:ee9d91e
Author:Jon Latane

Add Twilio/Bird SMS verification for phone ContactMethods Adds StartContactMethodVerification/VerifyContactMethod RPCs (self-only, 6-digit code, 10-minute expiry, 5-attempt cap, 60s resend cooldown), wires phone/email into UpdateUser (previously never persisted), and supports two pluggable SMS providers (Twilio, and Bird as a cheaper alternative) with admin-configurable preference ordering. UserProfilePage gets a collapsible Contact Methods section (deep-linkable via #contact-methods) with the verify flow; ServerInformationPage gets an admin-only Integrations tab for provider credentials and preference. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:ae7b56c
Author:Jon Latane

more stuff

Commit:b115709
Author:Jon Latane

Document RSS/Atom SyncSources and the /rss.xml, /atom.xml endpoints README.md and rellm.proto's SyncSources doc were still describing the pre-RSS/Atom world (iCal as the only source type, Event.sync_source, EventInstance.sync_source_instance_id) -- brings both up to date and adds RSS/Atom subsections alongside the existing iCal one. Also documents the new GET /rss.xml and GET /atom.xml (?user_id=-scoped) endpoints next to /calendar.ics's own docs in rellm.proto. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:433e9ef
Author:Jon Latane

Add SYNC_POSTS_FROM_RSS/ATOM permissions, RSS/Atom feed output, and synced-post edit lock Permissions: SYNC_POSTS_FROM_RSS (701)/SYNC_POSTS_FROM_ATOM (702), gating create/update SyncSource per its configuration's own type (required_sync_source_permission) instead of the single SYNC_EVENTS_FROM_ICS every source used to share. UserProfilePage's type selector now only offers kinds the viewer actually holds permission for, and the Sync Sources section's add row is gated on holding at least one of the three. Posts.editContentButton now hides for a Post with syncSource set (RSS/Atom-derived), same "would just be clobbered on next sync" reasoning EventPage.hasIcsSyncSource already applies to synced Events -- title/link have no separate edit affordance on postDetail to lock. Adds backend/src/web/rss_subscription.rs and atom_subscription.rs (GET /rss.xml, GET /atom.xml, both ?user_id=-scoped) serving Rellm's own Posts out as feeds -- the reverse direction of feed_sync, which pulls external feeds in. Mirrors ical_subscription.rs's shape. PostsPage.elm gets an Export popover mirroring EventsPage's ICS one, with RSS and Atom links plus side-by-side copy buttons. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:5b58c92
Author:Jon Latane
Committer:Jon Latane

Move SyncSource identity onto Posts; add RSS/Atom feed sync Denormalizes sync_source_id/uid/recurrence_anchor from events/event_instances down onto posts (a true 1:1 extension-table relationship), so any Post can now carry a SyncSource, not just an Event/EventInstance. Replaces the old single unique index with two partial ones (recurring vs. non-recurring) since Postgres never treats two NULLs as colliding, which would otherwise silently stop protecting a plain synced Post or an Event's own series-level Post from duplication - exactly the class of bug the 2026-09-04 duplicate-events incident was about. Adds feed_sync.rs (RSS/Atom via feed-rs, which parses both into one shape) alongside the existing event_sync.rs (ICS), dispatched by SyncSource.configuration's variant. delete_sync_source simplifies to a single posts-table operation that now works uniformly across content types. Event.sync_source and EventInstance.sync_source_instance_id are dropped from the wire (reserved) in favor of Post.sync_source, which every Event/EventInstance already carries via its own Post. Adds a source-type selector and updates the synced-count button to show post counts for RSS/Atom sources in UserProfilePage.elm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:10c1d4d
Author:Jon Latané
Committer:GitHub

Intra-cluster resource sharing (#107)

Commit:bae5b17
Author:Jon Latane

cluster resource locking, event timezones, a few small cross-protocol AccountsPanel UI tweaks

Commit:2b8b4b3
Author:Jon Latane

intra-cluster resource sharing

Commit:e26fa19
Author:Jon Latané
Committer:GitHub

Rename to Rellm (#104) * just update the first few paragraphs/link sections of README.md, so we have a tiny example * Rename Jonline to Rellm (Rust, Elm, LLM) Renames the project/protocol from Jonline to Rellm across the repo: proto package (protos/jonline.proto -> protos/rellm.proto), backend crate/binary, Elm/Tamagui/Flutter frontends (regenerated proto bindings, package names, source references), Docker image names, Homebrew/Linux launcher scripts (docs/homebrew_jonline.sh -> docs/rellm_homebrew.sh, linux_jonline.sh -> rellm_linux.sh), CI workflow, and k8s manifests/docs. The jonline.io domain itself is intentionally left untouched everywhere (it's a live hostname, not the project name) -- README now also lists a same-formatted Rellm.social entry alongside it. Includes deploys/rename_jonline_to_rellm.{sh,py}, the scripts used to do the bulk of this rename, kept for reference/future similar renames. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix remaining rename gaps: missed filenames and a live k8s namespace Ten hand-written source files (Flutter models, Tamagui about-page/service worker, and the elm-spa test file) had their *content* renamed to Rellm by the original rename pass but kept their old jonline_*/about_jonline filenames, silently breaking every by-path import into them (caught by elm-review's module-name check and tsc). Renamed the files to match. Also reverted the jonline.io deploy's Kubernetes *namespace* (`-n jonline`) in server_ci_cd.yml back from the blanket-renamed `-n rellm`: unlike the Deployment/StatefulSet/Service names inside it (safe to rename, since the Postgres/MinIO PVCs are referenced by static claimName, not derived from the StatefulSet name), Kubernetes has no rename-namespace operation -- actually renaming the namespace would mean recreating every resource in a new one, including the Postgres/MinIO data, for a purely internal id no user ever sees. Documented both fixes in the rename script for next time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix false-positive rename in backend/README.md "get.jonline"/"getjonline" is a display pun on the unrelated getj.online domain (a separate personal test deployment), not a reference to the Jonline protocol name -- the blanket rename script matched it by coincidental substring. Restored both to their original form. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Restore JONLINEIO cloudflare token * remove old load_balancer/Dockerfile --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

Commit:a1a05aa
Author:Jon Latane

Rename Jonline to Rellm (Rust, Elm, LLM) Renames the project/protocol from Jonline to Rellm across the repo: proto package (protos/jonline.proto -> protos/rellm.proto), backend crate/binary, Elm/Tamagui/Flutter frontends (regenerated proto bindings, package names, source references), Docker image names, Homebrew/Linux launcher scripts (docs/homebrew_jonline.sh -> docs/rellm_homebrew.sh, linux_jonline.sh -> rellm_linux.sh), CI workflow, and k8s manifests/docs. The jonline.io domain itself is intentionally left untouched everywhere (it's a live hostname, not the project name) -- README now also lists a same-formatted Rellm.social entry alongside it. Includes deploys/rename_jonline_to_rellm.{sh,py}, the scripts used to do the bulk of this rename, kept for reference/future similar renames. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:e9def3d
Author:Jon Latané
Committer:GitHub

Add Cross-Protocol Federation (BlueSky/Mastodon) (#103) * Prep work for trans-protocol support * tests passing * Denormalize SyncSource matching onto event_instances with a DB unique constraint Replaces the app-level events.info->>'sync_source_uid' JSON-key lookup (the root cause of the 2026-09-04 duplicate-events incident, where a plain code rename silently orphaned every pre-existing synced Event) with real, indexed columns on event_instances: sync_source_id, sync_source_uid, and sync_source_recurrence_anchor (split out of the old sync_source_instance_id composite string -- the anchor is an occurrence's stable identity within its series, distinct from its own starts_at once a RECURRENCE-ID override moves it). A unique index on (sync_source_id, sync_source_uid, sync_source_recurrence_anchor) makes a duplicate occurrence a hard DB constraint violation instead of a silent duplicate insert. Also fixes DeleteSyncSource's "detach but keep events" path, which didn't know about event_instances' own new sync_source_id FK, and adds regression tests proving both the resync-is-idempotent behavior and the DB constraint itself reject the exact failure mode from the incident. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add client-side Mastodon account connection flow Lets a user connect a Mastodon account entirely from the browser, no backend involvement: `UI.mastodonServersStrip` shows each instance the current server's admin has configured (`FederationInfo.mastodonServers`), with an alert badge if it has no app ID yet and a "Connect" button otherwise. Clicking it drives a new "mastodon" provider on the existing `Ports.facebookLoginPopup` OAuth-popup mechanism: since Mastodon has no fixed app to register against up front, the popup opens blank synchronously (to dodge popup blockers), then dynamically self-registers a throwaway app (`POST /api/v1/apps`), builds a PKCE S256 challenge, and only then navigates to the instance's real authorize URL. The resulting code is exchanged for a token directly against the instance (no `client_secret` needed, since PKCE alone authenticates a fresh per-attempt public client) and the connected account is verified via `GET /api/v1/accounts/verify_credentials` before being added to `Shared.AccountsPanel`. Verified Mastodon's `/api/v1/apps` and `/oauth/token` are both CORS-open (like Bluesky's equivalent endpoints), so this whole flow needs zero Jonline backend support. Scope for this pass: connecting an account and capturing its token. Connected accounts are session-only (not yet persisted across reloads) and aren't yet wired into any post-fetching/translation layer -- both are natural follow-ups once there's an actual reason to keep them around. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix yarn tsc: preserve federationInfo.mastodonServers on server settings save server_details_screen.tsx built a bare federationInfo literal with just servers, which used to slide by since facebookAuthConfig/xTwitterAuthConfig are optional fields -- but mastodonServers is a repeated (required, not optional) field in the generated type, so it failed type-checking outright once that field existed. Fixes it properly rather than just satisfying the compiler: spreads the existing federationInfo first (so facebook/x-twitter config isn't silently dropped either) and preserves mastodonServers explicitly, only overriding servers. `make test` doesn't run `yarn tsc` (only `yarn test`/vitest), which is why this slipped through the previous push -- confirmed clean now with both. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add client-side Bluesky account connection flow Mirrors the Mastodon connect flow's end state (a connected account with a captured token, session-only, not yet wired into anything) but with a much simpler mechanism underneath: Bluesky's com.atproto.server.createSession takes a handle and App Password directly -- no OAuth popup, no app registration, no PKCE. Just a plain inline form (UI.blueskyConnectSection), posting straight to bsky.social and landing the resulting handle/accessJwt in Shared.AccountsPanel.blueskyAccounts. The session response itself already carries the handle, so there's no separate verify-credentials round trip the way Mastodon's OAuth code needed. Known first-pass limitation, called out in BlueskyAccount's own doc comment: always calls bsky.social directly rather than resolving a handle to its actual PDS first, so a self-hosted-PDS account won't connect yet. Covers the common case (accounts hosted on bsky.social by default). No proto or backend changes needed -- this is Elm-only, same as the Mastodon flow ended up being. Verified with `make test` (backend/elm-review/tamagui/flutter all green) and `yarn tsc` (not covered by `make test`, per the previous fix on this branch) before pushing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add Mastodon/Bluesky post translation + bootstrap Elm tests Translation layer: Shared.Federation.Mastodon/Bluesky decode each protocol's own API shape (Mastodon's Status entity, a Bluesky feedViewPost) and translate it into a real Jonline Post -- namespaced ids ("mastodon:host:id" / "bluesky:at://...") so they can never collide with a real Jonline post's own id, GLOBALPUBLIC visibility (both sources are definitionally public), REPLY/POST context from each protocol's own reply signal, and avatars carried via MediaReference.url (added earlier this branch) rather than a Jonline media id, since they're not and never will be Jonline-hosted. Deliberately out of scope for this pass: actually fetching from a live page and merging into PostsPage's postsByServer. That page's fetch triggers are scattered across many call sites (tab changes, account toggles, new-post insertion, ...), and wiring a translated feed in safely deserves its own pass rather than a rushed edit riding on top of this one -- decoder/toPost are complete and fully tested on their own. Elm tests: these are the first in the app -- bootstrapped elm-test (elm-explorations/test, rtfeldman/elm-iso8601-date-strings promoted to a direct dependency) and wired it into `make test` via a new elm_test Makefile target. Added Support.MastodonFactory/BlueskyFactory (paired JSON-string + already-decoded fixtures built from one shared Overrides record, so decoder tests can check the two agree) and Support.PostFactory (a thin default-Post wrapper for the two fields sorting tests care about). JonlineTests covers the main protocol's own Components.Posts.postTimestamp -- including the actual point of this feature: a real Jonline post and translated Mastodon/Bluesky posts sorting correctly together, not just within their own protocol. Verified with `make test` (backend/elm-review/elm-test/tamagui/flutter all green) and `yarn tsc` before pushing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Dedupe nonEmpty and Http error-resolver boilerplate - Shared.Federation.Common.nonEmpty replaces the identical copy in both Mastodon.elm and Bluesky.elm. - Shared.AccountsPanel.jsonResolver factors out the GoodStatus_/NetworkError_/Timeout_/BadUrl_ boilerplate that verifyMastodonCredentialsTask and createBlueskySessionTask had duplicated, parameterized by a decoder and a BadStatus_-specific error builder (bare status code for Mastodon, a parsed message for Bluesky) so each keeps its own error-shape handling. Verified with `make test` (all green) and `yarn tsc` before pushing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Wire Mastodon/Bluesky translated posts into the live Posts feed Adds Mastodon.fetchPosts/Bluesky.fetchPosts (fetch + toPost, composed via the now-shared Shared.Federation.Common.jsonResolver) and calls them from Components.Pages.PostsPage.fetchFederatedPosts for every connected account, storing results in a new model.federatedPosts dict keyed by a synthetic host ("mastodon:instanceHost" / "bluesky:handle") that can never collide with a real AccountsPanel.Server.frontendHost. That synthetic-host trick is what makes the rest of this a small, additive change rather than a rewrite: postCardView already derives maybeServer/maybeAccount via AccountsPanel.serverForHost/ enabledAccountForServer, plain host-keyed lookups that already return Nothing for an unrecognized host -- so a federated post renders through the exact same Posts.postCard as everything else, with reply/star/ sync-destination actions gracefully disabled for free, no special-casing needed there. Scope, deliberately: only shown on the plain, unscoped "Posts" feed (same condition recentPostsTabsView uses to show its own tabs), since a federated fetch supports none of the author-scoping, text search, or PostsBeforeDate cutoff filtering a real GetPosts request does -- showing it anywhere those apply would be misleading. Fetched once at page load, not re-fetched on tab/search/cutoff changes (none of which would change the result), and only re-filtered by model.context at merge time in syncAnimations, since that's the one dimension that can still apply to an already-fetched federated feed. Verified with `make test` (all green) and `yarn tsc` before pushing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Let Mastodon instances be browsed anonymously, no account needed Adds a "just browse this instance's public timeline" affordance (UI.mastodonBrowseSection) alongside the existing OAuth "Connect" flow -- type a host, add it, no popup, no admin-registered app, no account at all, since Mastodon.fetchPosts already hits a plain unauthenticated GET (it never used the connected account's accessToken in the first place). Mirrors the servers strip's own "type a host, add it" shape for real Jonline servers, minus the connectivity validation a real server add does -- there's nothing to check ahead of time, a bad host just silently fails to load posts. AccountsPanel.browsedMastodonInstances is a bare List String (no richer record needed -- there's no connection state or credential to track). PostsPage.fetchFederatedPosts now fetches the deduplicated union of connected accounts' instanceHosts and browsed instances, since both hit the exact same endpoint -- no reason to fetch a host twice just because it's both connected and browsed. Verified with `make test` (all green) and `yarn tsc` before pushing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fix startup panic on legacy federation_info + wire Mastodon defaults Bug: any existing server_configurations row persisted before mastodon_servers existed panicked the whole server on startup -- ToProtoServerConfiguration::to_proto's serde_json::from_value(...) .unwrap() hit "missing field mastodon_servers". Unlike facebook_auth_config/x_twitter_auth_config (both optional, so serde already treats a missing key as None for free), mastodon_servers is a repeated field (Vec<MastodonServer>), which serde treats as a hard error when absent. Fixed with the same #[serde(default)] field_attribute this codebase already used for exactly this problem once before (EventSettings' two newest fields, see build.rs) -- added a regression test alongside the existing custom_tabs migration tests proving legacy federation_info still deserializes. Also wires up MastodonServer.configured_by_default/pinned_by_default, which existed in the proto but had no client behavior yet: the first time a browsing host is ever seen (same moment FederatedServer's own configured_by_default/pinned_by_default already auto-connects real federated servers), any MastodonServer marked either flag now gets added to browsedMastodonInstances automatically -- no negotiation needed, unlike a real server, since browsing an instance is just an unauthenticated GET. Documented (in both the proto and the Elm code) that the two flags aren't currently distinguishable for Mastodon, since browsedMastodonInstances has no disabled-but-present state the way Server.enabled gives pinned_by_default extra meaning over configured_by_default for real servers. Verified with `make test` (all green, including the new regression test) and `yarn tsc` before pushing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Document Trans-Protocol Federation; fix federation accuracy in README Fills in the previously-empty "Trans-Protocol Federation" sections in both README.md and jonline.proto's doc comments (Mastodon/ActivityPub and BlueSky/AT Protocol), and corrects two related inaccuracies: - "Inter-Server Federation"/"Delightful Federation" described Federated Servers as a form of "server-to-server communication," then cited CORS as the safeguard against it -- CORS only matters for *client* cross-origin calls, confirming this is actually client-driven, not server-to-server. Reworded to say Jonline servers never talk to each other directly, and pointed to Sync Destinations as the one place the backend genuinely does initiate outbound calls to other platforms. - Added a "Jonline as a protocol vs. Bluesky/AT Protocol" section and a paragraph in the ActivityPub comparison addressing whether Jonline is "isomorphic to a superset of ActivityPub": true of the object model (Events/Groups/Media as first-class types), not true of the federation mechanism (no server-to-server delivery protocol at all). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Unify real-server and Mastodon/Bluesky post fetching under one FeedSource type PostsPage previously ran two entirely separate fetch-and-store pipelines: postsByServer/GotServerPosts/fetchNewServers/refetchServers for real Jonline servers, and federatedPosts/GotFederatedPosts/fetchFederatedPosts for Mastodon/Bluesky, merged only at the last step in syncAnimations. Introduces FeedSource (JonlineServer/MastodonInstance/BlueskyFeed) as the one type both paths now dispatch over, collapsing GotServerPosts/ GotFederatedPosts into a single GotFeedPosts Msg (normalized via a small FeedResult type, since Grpc.Error/GetPostsResponse and Http.Error/List Post have no content worth keeping once a fetch fails) and refetchServers/ fetchNewServers into refetchFeeds/fetchNewFeeds, generalized over FeedSource instead of just AccountsPanel.Server. federatedPosts as a separate Model field is gone -- Mastodon/Bluesky feeds now live in postsByServer under their existing synthetic host keys, using the same ServerFeed status tracking real servers get. Search text/date-cutoff refetches (applySearchChange, TabChanged, GotNow, PublishedBeforeDebounceElapsed) stay deliberately scoped to real servers only -- Mastodon/Bluesky support neither, so refetching them there would just be wasted, unfiltered-anyway network calls. One behavior change falls out of the unification for free: a newly-connected Mastodon instance/ Bluesky account now shows up live (via the same Poll/SharedMsg path real servers use) instead of only on this page's next (re)visit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Flesh out Mastodon/ActivityPub server config docs; rename to Cross-Protocol Federation Documents FederationInfo.mastodon_servers in jonline.proto's Server Configuration section: what it's for (registering a per-instance OAuth app so users can connect their own Mastodon account -- unlike anonymous browsing, which needs no config at all), why it's per-instance (Mastodon has no central OAuth authority the way Facebook/X do), and how configured_by_default/pinned_by_default mirror FederatedServer's own fields but govern anonymous browsing instead of account auto-connect. Also renames "Trans-Protocol Federation" to "Cross-Protocol Federation" throughout README.md/jonline.proto (and regenerated docs/bindings) -- "cross-protocol" is the more idiomatic term for the same feature. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add run_flutter, build_* targets, and a run_tmux dev launcher Fills out the run_backend/run_elm/run_tamagui set with run_flutter, and adds a parallel build_backend/build_elm/build_tamagui/build_flutter set -- tamagui/flutter had no plain `build` target yet, so those got thin aliases (tamagui's `build` -> existing `rebuild_fe`; flutter's `build` -> existing `build_web`) rather than duplicating logic. flutter also had no `run` target at all (added `fvm flutter run`, matching its own README's documented dev command). run_tmux launches backend (left) + Elm (right) in one tmux session with mouse mode on (click a pane to focus/resize instead of needing tmux's own keybindings) and pane-border-status titles labeling which is which. automatic-rename is turned off and the window/terminal title pinned to "jonline: run_tmux", since tmux otherwise relabels the window after whatever's currently in the foreground (make, then cargo/npx, etc.). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Catch a missed case variant in the Trans-Protocol -> Cross-Protocol rename The earlier rename's sed patterns matched "Trans-Protocol Federation" and "trans-protocol federation" but missed the mid-sentence, sentence-cased "Trans-protocol federation" in the BlueSky/AT Protocol doc paragraph. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * update README * docs and stuff --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

Commit:62dab1c
Author:Jon Latane

docs and stuff

Commit:645313f
Author:Jon Latane

Catch a missed case variant in the Trans-Protocol -> Cross-Protocol rename The earlier rename's sed patterns matched "Trans-Protocol Federation" and "trans-protocol federation" but missed the mid-sentence, sentence-cased "Trans-protocol federation" in the BlueSky/AT Protocol doc paragraph. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:640f5c6
Author:Jon Latane

Flesh out Mastodon/ActivityPub server config docs; rename to Cross-Protocol Federation Documents FederationInfo.mastodon_servers in jonline.proto's Server Configuration section: what it's for (registering a per-instance OAuth app so users can connect their own Mastodon account -- unlike anonymous browsing, which needs no config at all), why it's per-instance (Mastodon has no central OAuth authority the way Facebook/X do), and how configured_by_default/pinned_by_default mirror FederatedServer's own fields but govern anonymous browsing instead of account auto-connect. Also renames "Trans-Protocol Federation" to "Cross-Protocol Federation" throughout README.md/jonline.proto (and regenerated docs/bindings) -- "cross-protocol" is the more idiomatic term for the same feature. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:3c8cab6
Author:Jon Latane

Document Trans-Protocol Federation; fix federation accuracy in README Fills in the previously-empty "Trans-Protocol Federation" sections in both README.md and jonline.proto's doc comments (Mastodon/ActivityPub and BlueSky/AT Protocol), and corrects two related inaccuracies: - "Inter-Server Federation"/"Delightful Federation" described Federated Servers as a form of "server-to-server communication," then cited CORS as the safeguard against it -- CORS only matters for *client* cross-origin calls, confirming this is actually client-driven, not server-to-server. Reworded to say Jonline servers never talk to each other directly, and pointed to Sync Destinations as the one place the backend genuinely does initiate outbound calls to other platforms. - Added a "Jonline as a protocol vs. Bluesky/AT Protocol" section and a paragraph in the ActivityPub comparison addressing whether Jonline is "isomorphic to a superset of ActivityPub": true of the object model (Events/Groups/Media as first-class types), not true of the federation mechanism (no server-to-server delivery protocol at all). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:589fc7b
Author:Jon Latane

Fix startup panic on legacy federation_info + wire Mastodon defaults Bug: any existing server_configurations row persisted before mastodon_servers existed panicked the whole server on startup -- ToProtoServerConfiguration::to_proto's serde_json::from_value(...) .unwrap() hit "missing field mastodon_servers". Unlike facebook_auth_config/x_twitter_auth_config (both optional, so serde already treats a missing key as None for free), mastodon_servers is a repeated field (Vec<MastodonServer>), which serde treats as a hard error when absent. Fixed with the same #[serde(default)] field_attribute this codebase already used for exactly this problem once before (EventSettings' two newest fields, see build.rs) -- added a regression test alongside the existing custom_tabs migration tests proving legacy federation_info still deserializes. Also wires up MastodonServer.configured_by_default/pinned_by_default, which existed in the proto but had no client behavior yet: the first time a browsing host is ever seen (same moment FederatedServer's own configured_by_default/pinned_by_default already auto-connects real federated servers), any MastodonServer marked either flag now gets added to browsedMastodonInstances automatically -- no negotiation needed, unlike a real server, since browsing an instance is just an unauthenticated GET. Documented (in both the proto and the Elm code) that the two flags aren't currently distinguishable for Mastodon, since browsedMastodonInstances has no disabled-but-present state the way Server.enabled gives pinned_by_default extra meaning over configured_by_default for real servers. Verified with `make test` (all green, including the new regression test) and `yarn tsc` before pushing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:ed2ca3b
Author:Jon Latane

Prep work for trans-protocol support

Commit:b854e6c
Author:Jon Latané
Committer:GitHub

Simplify kubernetes background jobs, event filter admin setting, simplify federated auth (#101) * K8s fixes * show_started_or_long_events_by_default admin flag * docs * simplify auth tokens * more stuff * calendar rendering and docs

Commit:1d539bd
Author:Jon Latane

calendar rendering and docs

Commit:599327d
Author:Jon Latane

more stuff

Commit:6f1b20e
Author:Jon Latane

simplify auth tokens

Commit:c356da6
Author:Jon Latane

docs

Commit:5b5a7fe
Author:Jon Latane

show_started_or_long_events_by_default admin flag

Commit:e0788bf
Author:Jon Latane

Merge branch 'main' into simplify-kubernetes-background-jobs

Commit:26d7013
Author:Jon Latane

K8s fixes

Commit:e0fba7f
Author:Jon Latané
Committer:GitHub

AI Image Generator, eliminate separate event_ids and event_instance_ids (all are just post_ids) (#100) * revamp * docs * Gemini Nano Banana/OpenAI Image image generation * capability awareness, overage and other refinements * UserPreferences postsBefore and eventsAfter defaults * more rigorous custom tab stuff * Collapse events.id/event_instances.id into their post_id Events and EventInstances each carried a redundant surrogate id alongside a post_id that was already a required, unique 1:1 FK to posts. Drops the surrogates and makes post_id the primary key for both tables, removes the now-redundant Event.id/EventInstance.id/GetEventsRequest.event_id/ event_instance_id proto fields (post_id is a strict superset of the last two), and propagates the identity change through the Rust backend, Elm SPA (Pages/Event/EventId_.elm -> PostId_.elm), and Tamagui frontend. Flutter needed no changes -- its event screens are unwired scaffolding. Also fixes two search_text propagation triggers that independently joined events by its old surrogate id. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add short Post/Event URLs (/{postId}) Any path segment starting with a character no username or custom tab path could legally start with (-._~:/?[]@!$&'()*+,;%=, deliberately excluding # since fragments never reach the server) now resolves as a short alias for /post/:id or /event/:id -- rendering the same content in place without redirecting the address bar. Enforced server-side in validate_username/validate_custom_tab_path so new usernames/custom tabs can never collide with the reserved set. Elm: extracts Components/Pages/EventPage.elm out of Pages/Event/PostId_.elm (mirroring the existing PostPage split), adds Components/Pages/PostOrEventPage.elm to disambiguate and embed the right one, and wires the dispatch into UsernameOrCustomTab_.elm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * run make * Remove dead UI.CustomNav.homeTarget and unused CalendarDisplayMode alias Pre-existing elm-review failures (make test_elm), unrelated to recent work: homeTarget was fully superseded by homeConfig with no remaining callers, and the CalendarDisplayMode module alias had no qualified uses left. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

Commit:5ee950b
Author:Jon Latane

Add short Post/Event URLs (/{postId}) Any path segment starting with a character no username or custom tab path could legally start with (-._~:/?[]@!$&'()*+,;%=, deliberately excluding # since fragments never reach the server) now resolves as a short alias for /post/:id or /event/:id -- rendering the same content in place without redirecting the address bar. Enforced server-side in validate_username/validate_custom_tab_path so new usernames/custom tabs can never collide with the reserved set. Elm: extracts Components/Pages/EventPage.elm out of Pages/Event/PostId_.elm (mirroring the existing PostPage split), adds Components/Pages/PostOrEventPage.elm to disambiguate and embed the right one, and wires the dispatch into UsernameOrCustomTab_.elm. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:53a1223
Author:Jon Latane

Collapse events.id/event_instances.id into their post_id Events and EventInstances each carried a redundant surrogate id alongside a post_id that was already a required, unique 1:1 FK to posts. Drops the surrogates and makes post_id the primary key for both tables, removes the now-redundant Event.id/EventInstance.id/GetEventsRequest.event_id/ event_instance_id proto fields (post_id is a strict superset of the last two), and propagates the identity change through the Rust backend, Elm SPA (Pages/Event/EventId_.elm -> PostId_.elm), and Tamagui frontend. Flutter needed no changes -- its event screens are unwired scaffolding. Also fixes two search_text propagation triggers that independently joined events by its old surrogate id. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

Commit:27ab847
Author:Jon Latane

more rigorous custom tab stuff

Commit:7975115
Author:Jon Latane

UserPreferences postsBefore and eventsAfter defaults

Commit:c0177cc
Author:Jon Latane

capability awareness, overage and other refinements