First-party frontend
Roosty’s first-party frontend uses Leptos 0.8 and Tailwind CSS 4. The existing Axum process renders complete HTML on the server, serves the generated CSS, JavaScript, and WebAssembly assets, and exposes narrowly scoped server functions for hydrated interactions. Caddy remains a reverse proxy and does not own frontend routing.
Component system
The first-party UI uses daisyUI 5 components and standard Tailwind utilities. Roosty overrides only the colors of
daisyUI’s light and dark themes, retaining its green accent and neutral surfaces with automatic system color-scheme
behavior. Typography, radii, sizing, borders, depth, motion, and component behavior use framework defaults. Reusable
Leptos components own repeated navigation, account menus, cards, headings, administrator panels, fields, notices,
and confirmation controls, while native links, buttons, inputs, forms, tables, and details elements retain their
browser semantics.
New controls and layouts should use daisyUI component classes and standard Tailwind utilities instead of authored CSS
selectors. The stylesheet is reserved for loading Tailwind and daisyUI and declaring the two Roosty color palettes.
The daisyUI standalone build plugins are pinned and vendored below crates/web-ui/style/vendor because Cargo Leptos
uses Tailwind’s standalone executable. Update both plugin files, their checksums, and the versioned license link
together when upgrading daisyUI.
Route ownership
UI routes are registered explicitly from the Leptos route list. In addition to / and /about,
local accounts have /@username, /@username/with_replies, /@username/media, and
/@username/tagged/hashtag profile timelines. Local status permalinks use
/@username/status-id. Direct requests and browser refreshes therefore use the same server
renderer as client navigation. Generated assets live below /pkg, and internal UI server
functions live below /api/web. Roosty deliberately has no global single-page-app fallback so
missing Mastodon API and ActivityPub routes keep their existing response semantics.
The browser routes are separate from the JSON-only ActivityPub actor and Note identifiers at
/users/username and /users/username/statuses/status-id; those protocol routes do not use
content negotiation.
Rendering and SEO
SEO-relevant content must be present in the initial response. Each public route owns its title, description, canonical URL, and Open Graph metadata through Leptos Meta. Route data should use a blocking SSR resource when it affects visible content or metadata; hydration reuses the serialized resource result instead of replacing server-rendered content after startup.
Profile pages include profile metadata, fields, counts, featured tags, pins, and 20-post UUID-cursor pages. “Load more” appends and deduplicates posts after hydration and replaces the cursor query in browser history. Profile roots omit replies; the replies, media, and tagged tabs apply their named filters. Hydrated tab navigation preserves the profile identity card and tab bar while only the selected timeline loads. Status pages render the visible bounded mixed local/remote thread, content warnings, sensitive media, accessible descriptions, polls, quotes, and preview cards.
Anonymous documents expose public and unlisted posts. A signed-in local account can additionally see follower-only and direct posts permitted by its follow, recipient, conversation, ownership, and block state. These reads use a read-only database snapshot. Session-aware documents and loaders are private, uncached, and vary on the session cookie. Missing, deleted, suspended, inaccessible, and author-mismatched resources all return the same not-found response.
Profile and article documents include canonical URLs without cursor queries, Open Graph data,
ActivityPub alternate links, fediverse:creator, and Mastodon-compatible h-card/h-entry
microformats. Private pages, later cursor pages, limited accounts, and non-discoverable profiles
are marked noindex. Sensitive status metadata contains only the content warning and never a
preview image.
When global search indexing is enabled, eligible profile roots include typed ProfilePage JSON-LD
and eligible status permalinks include Google-supported SocialMediaPosting JSON-LD with authors,
interaction counters, media, edits, and safe visible replies. Sensitive posts never receive
JSON-LD or sitemap promotion. Media-only posts receive posting markup only when an image or a
complete video representation is available. /robots.txt advertises /sitemap.xml, whose
cursor-addressed profile and post sitemap chunks contain at most 50,000 canonical URLs each.
Profiles must be active, discoverable, and unlimited; promoted posts must additionally be public
and non-sensitive.
After deployment, validate representative profile, text, image, video, edited, and reply-thread
URLs with Google’s Rich Results Test. Submit /sitemap.xml in Search Console, inspect live URLs,
and request recrawling where appropriate. Search indexing and rich-result display remain at
Google’s discretion.
The welcome and about pages present the operator-configured instance name and description rather than Roosty project marketing. A missing or blank description uses neutral social-web copy. Roosty appears as software attribution with its release version in the shared footer.
The shared roosty-web-ui crate is compiled once with ssr for the native server and once with
hydrate for wasm32-unknown-unknown. Server-only database and authentication services cross that
boundary through UiBackend; they are never compiled into or exposed to the browser bundle.
Authentication
The first-party UI uses Roosty’s signed, secure, HTTP-only session cookie. It is not registered as an OAuth client and does not store bearer tokens in browser storage. A read-only bootstrap server function projects only public instance metadata and a small optional account summary. Invalid, expired, and deleted-account sessions render as anonymous.
Leptos renders the /login, /auth/edit, and OAuth authorization views, while the existing server
handlers remain authoritative for credential verification, session cookies, password updates, and
OAuth grants. Native HTML form submissions use POST/Redirect/GET; fixed Strum-backed values select
user-facing results and OAuth consent decisions without putting credentials or arbitrary errors in
URLs. Login return paths remain sanitized and same-origin. The password page and OAuth consent flow
redirect anonymous visitors through login.
Links to server-owned account routes use rel="external" so Leptos performs a full document
navigation instead of resolving them as client-side UI routes. Future state-changing UI server
functions must validate both the session and a CSRF token.
Administration
Administrators have an SSR and hydrated operations interface with a responsive category menu:
work queue at /admin, local accounts at /admin/accounts, remote accounts at
/admin/remote-accounts, and audit log at /admin/audit-log. The navigation link is projected
only for accounts whose persisted is_admin flag is set, and the server independently protects
every admin route. Anonymous requests return through login; authenticated non-administrators
receive a forbidden response.
Each category has a separate JSON GET loader and queries only the data it presents. The active
category refetches every 15 seconds while the document is visible, and its Refresh button triggers
the same in-place request without a page reload. These responses are marked Cache-Control:
no-store. Queue data exposes only job kind, lifecycle state, attempts, scheduling timestamps, and
bounded error text. Raw job payloads are never sent to the UI. Process-local Prometheus metrics
remain infrastructure endpoints.
Account creation, password reset, and local or cached-remote account limits use native forms with a session-bound HMAC CSRF token. Sensitive account actions open a daisyUI confirmation modal before the native form is submitted. Mutations and their audit records commit in one transaction. Generated temporary passwords are returned only by the successful response and are not stored in audit metadata.
OAuth views
OAuth consent and out-of-band code results use SSR-only Leptos documents with the shared instance
chrome and stylesheet. They intentionally use native forms without hydration: consent needs no
browser-side state, and an out-of-band one-time code must stay in the POST response rather than a
redirect URL. Consent lists the requested scopes and supports typed approve or deny decisions;
regular denials return access_denied to the exact registered callback, while out-of-band denials
render a local result. Elk and Phanpy remain independent full Mastodon clients.
Packaging and failures
Install Cargo Leptos 0.3.7 and the wasm-bindgen CLI matching the workspace’s wasm-bindgen
dependency (currently 0.2.126). Cargo Leptos downloads the pinned Tailwind CSS 4.3.3 standalone
CLI when LEPTOS_TAILWIND_VERSION=v4.3.3 is set. The vendored daisyUI plugins are loaded by that
standalone executable, so frontend builds do not require Node.js dependencies. Set that variable alongside
LEPTOS_WASM_OPT_VERSION=version_131 when invoking Cargo Leptos; CI, release, and container builds
set both automatically. Because Cargo Leptos prefers an
existing executable, any wasm-opt already on PATH must also be version 131. cargo leptos build
then produces the native binary and target/site. Container and archive releases ship both, and
ROOSTY_WEB_ROOT locates the site directory at runtime. The backend serves /pkg from that
directory.
The hydrated entry point installs browser-panic-hook before starting Leptos. Browser panics keep
their diagnostics in the console while replacing the page body with a small, non-diagnostic
recovery view that works without further WebAssembly execution.