Docs Surfaces
Mood
The three-level mood surface: home preview, feed wheel, and detail page.
Scope
This document covers:
L1mood feed at/moodL2mood detail at/mood/[id]- the feed and comments APIs they depend on
- shared Telegram parsing and mood shaping
- embed, RSS, and subscribe entrypoints
Mood API Taxonomy
- v2 (
/api/v2/mood*) is the D1 archive and the default base reader for public mood pages. - v1 (
/api/v1/mood*) is the live Telegram mirror for comments, reactions, freshness checks, and archive fallback. ?api-v2=trueis deprecated migration scaffolding. Do not add it to canonical docs, RSS links, oEmbed targets, or user-facing URLs.api.buxx.meis machine ingress, not the canonical public API surface. The public contract remains thebuxx.mepages and compatibility JSON routes.
Route Map
Main files:
src/pages/mood.astrosrc/pages/mood/[id].astrosrc/pages/mood/embed.astrosrc/pages/mood/rss.xml.tssrc/pages/mood/subscribe.astro
Routing rules:
/moodis dynamic and not prerendered./mood/[id]is dynamic and not prerendered./moodis indexable;/mood/[id]emitsnoindex, followso the feed remains discoverable without letting the unbounded detail archive crowd out editorial pages in search results./mood/[id]?embed=1redirects to/mood/embed?id=...&theme=...&link=false./mood/rss.xmlemits RSS from the feed source./mood/subscriberedirects to/mood?subscribe=1.
L1 Feed
Entry file: src/pages/mood.astro
Page-level responsibilities:
- hides the normal home section navbar
- injects header actions into the shared layout:
- RSS
- Telegram
- Notify
- composes
src/features/mood/ui/TimelineWheel.astro,src/features/mood/ui/FeedShell.astro, andsrc/features/mood/ui/NotifyPanel.astro - bootstraps
src/features/mood/client/feed-controller.ts,src/features/mood/client/notify-panel-controller.ts, andsrc/features/mood/client/timeline-wheel.ts
Feed data flow:
- The browser requests
GET /api/v2/moodwhen the page is archive-backed. - The response contains channel metadata plus feed-shaped posts.
src/features/mood/client/feed-media-hydration.tshydrates the channel hero and deferred media behavior.src/features/mood/client/feed-renderer.tsgroups posts by date and appends them into the feed.- Infinite loading continues in either direction against the active source.
Anchor URLs represent a midpoint in the same continuous feed. Older pages use
before=<oldestPostId> and newer pages use after=<newestPostId>; both APIs
return the adjacent window in descending display order. Transient archive
failures are retried before the client falls back to the live reader.
Freshness behavior:
- the page polls
GET /api/moods?probe=1&fresh=1every 75 seconds - if a newer post exists, the page shows an update notice
- refresh can happen automatically when the user is near the top
Feed API
Implementation owner: site-api /api/moods
Upstream dependency:
- the D1 archive through
GET /api/v2/mood, with the live reader as fallback
Returned post shape is optimized for feed rendering:
previewTextpreviewHtmlimageimageFallbackmediaHtmlneedsDetailPageforwardedFromquotereactionscommentsCount
Important shaping rules:
needsDetailPagebecomestruewhen there is no inline media preview and the post is either long text or media-heavy.- primary image URLs prefer
PUBLIC_HD_IMAGE_URL. - fallback image URLs point at Telegram media through the site proxy when needed.
- archived replies preserve a quote edge whose link resolves to the parent mood pathname.
- the feed can run in E2E fixture mode instead of the live source.
Feed Rendering Strategy
Most feed items are still created client-side, but the route no longer owns the DOM logic directly.
Current client entrypoints:
src/features/mood/client/feed-controller.tssrc/features/mood/client/feed-renderer.tssrc/features/mood/client/feed-media-hydration.tssrc/features/mood/client/feed-update-watcher.tssrc/features/mood/client/feed-comments-popover.ts
Rendering behavior:
- posts are grouped by day
- inline media stays expanded in the feed
- long text-only posts clamp and link to detail
- visible archive posts hydrate live comments/reactions through
GET /api/v2/moods/live-counts - hovering the comments badge lazily fetches comment previews
Comment preview path:
- fetches
GET /api/comments?postId=... - shows up to 3 comments in a popover
- links to
/mood/{id}#comments
L2 Detail
Entry file: src/pages/mood/[id].astro
Server-side responsibilities:
- fetches one post by id through the archive reader, with a live-reader fallback
- sets
404when the post is missing - renders a controlled not-found or unavailable state instead of crashing
- composes
src/features/mood/ui/DetailArticle.astro, which in turn mountssrc/features/mood/ui/CommentsSection.astro
Rendering behavior:
src/features/mood/ui/DetailArticle.astroinserts gallery-aware HTML withset:html={renderedPostContent}- forwarded metadata, reactions, and tags are rendered from parsed Telegram data
- the page can show a Telegram
Leave a commentCTA when channel config exists
Back navigation:
- prefers browser history when available
- otherwise falls back to
/mood
Comments
Implementation files:
site-api /api/commentssrc/features/mood/client/detail-comments-controller.tssrc/features/mood/shared/comments.ts
Data flow:
src/features/mood/ui/CommentsSection.astrorenders a skeleton comments section.src/features/mood/client/detail-comments-controller.tsfetchesGET /api/comments?postId=....- API validates
postIdand optionalbefore. - API reads the live Telegram mirror through the canonical v1 mood path.
- Client renders sanitized comments and paginates with
before=<commentId>.
Comment normalization:
- reply blocks become quote cards
- loose text nodes are wrapped into paragraphs
- avatar and image URLs are sanitized before insertion
- duplicate comment ids are filtered client-side
Mood Shaping
Read source — archive base, live hydration
Public feed and detail pages read D1 archive content by default through MOOD_READ_SOURCE=archive.
The site falls back to the bounded live reader if an archive call fails; comments, freshness probes,
and visible reactions/counts stay live. Set MOOD_READ_SOURCE=live for rollback, or use
?source=live|archive for an uncached request-level override.
Core files:
Machine-ingress responsibilities:
- expose
/api/v1/mood*as the live Telegram mirror - expose
/api/v2/mood*as the D1 archive / structured read - ingest Telegram webhook updates into D1 for backup, search, AI, and debugging
- normalize media URLs into
https://buxx.me/api/v2/images/* - keep
api.buxx.meas machine ingress rather than the canonical public API surface
src/features/mood/shared/utils.ts responsibilities:
- strip Telegram HTML into preview text
- keep a limited preview HTML subset
- extract first image and fallback image
- detect media-heavy or long posts
- derive quote preview data
- group posts by date
Embed, RSS, and Subscribe
Embed file: src/pages/mood/embed.astro
Supported embed query parameters:
idcountthemeframedensityfontoriginrefreshlink
RSS file: src/pages/mood/rss.xml.ts
RSS behavior:
- fetches the mood list
- sorts by numeric post id descending
- emits up to 50 items
- absolutizes URLs inside content HTML
- emits full
content:encoded
Subscribe entry:
/mood/subscribeonly redirects into the feed UI- actual subscribe, confirm, unsubscribe, schedule, retry, and dispatch logic lives in the notify routes
Security and Limits
Relevant file: src/lib/security/rate-limit.ts
Current limits:
/api/moods: 180/min normally/api/moods?fresh=1: 30/min/api/moods?probe=1: 90/min/api/comments: 90/min
Validation rules:
before,after,postId, and comment cursors must be numeric- invalid cursors return
400 - limit violations return
429with rate-limit headers
Important constraint:
- rate limiting is in-memory per instance, not shared across deployments
Operations Health
Ordinary archive ingestion preserves a known positive reply_to when a lower-fidelity update omits the relationship. The authenticated mood health response exposes replyIntegrity with the edge count, unresolved same-channel targets, a capped child-ID sample, the number of live posts never reconciled, and the oldest verification timestamp.
The scheduled ops suite checks configured reply canaries against strict archive detail reads using
fresh=1&fallback=0. MOOD_REPLY_CANARIES is a capped comma-separated list of positive integer
child:parent mappings; each child must expose a quote whose URL pathname is /mood/<parent>.
The production workflow currently uses 1609:1600.
Edge Cases
- missing detail pages still render a controlled fallback UI
- feed rendering deduplicates post ids
- comment pagination deduplicates comment ids and stops when Telegram stops giving a usable cursor
- E2E fixtures can replace live Telegram calls for feed, detail, and comments
- animated Telegram emoji enhance progressively and fall back safely when loading fails