Revolt Frontend

Welcome to the developer documentation for the Revolt frontend (this covers functionality and general guidelines for Revolt for Web as well as other platforms we build for).

This is very much incomplete and needs more work!

Documentation Contribution Guidelines

This list is also very much incomplete, but it'll grow as time goes on:

  • Convert all assets to WebP when contributing new images.

Contribution Guide

If you have not yet read it, read the main contribution guide.

You can get started with Revolt for Web by following the development guide on the repository.

Contribution Policy

Since this project is still heavily in development, things may change rapidly at little notice, so it's recommended that you join the Revolt server and the Revolt development server where you can talk in #Revolt for Web about this project.

If something is explicitly marked as 'help wanted', then you can likely just pick up the issue and contribute a PR, however, you should make sure to communicate this on the issue so others don't start on the same issue as you, causing issues later.

Otherwise, feel free to come ask about what you'd like to contribute and run it past a maintainer so that we can check if there are any potential conflicts.

Feature Matrix

Comparison of implemented features across Revolt's clients.

CategorySubcategoryFeature ReviteFrontendAndroidiOSPriority
AuthorisationLogin Log into an accountP0 Must
   Create an accountP0 Must
   Send password resetP0 Must
   Resend email verificationP0 Must
   Confirm password resetUnapplicable
   Confirm email verificationUnapplicable
   Confirm account deletionUnapplicable
 Multi-Factor Authentication Use PasswordP0 Must
   Use TOTPP0 Must
   Use RecoveryP0 Must
 Session Lifecycle Spec CompliantP0 Must
HomeGeneralHomeLaunch PageP0 Must
   Saved NotesP0 Must
  FriendsList Friends & BlockedP0 Must
   List Pending RequestsP0 Must
   Accept Requests🚧P0 Must
   Send Requests🚧P0 Must
   Remove / Block Users🚧P0 Must
   Unblock Users🚧P0 Must
   Quick Actions for UsersP0 Must
  User ProfileShow ProfileP0 Must
   Mutual FriendsP1 Preferred
   Mutual GroupsP1 Preferred
   Mutual ServersP1 Preferred
  GroupsList ConversationsP0 Must
   Create GroupP0 Must
   Show Group MembersP0 Must
   Edit SettingsP0 Must
ServersServer List User HomeP0 Must
   Unread ConversationsP0 Must
   List ServersP0 Must
   Reorder Servers🚧P1 Preferred
   Create ServerP0 Must
   Join ServerP0 Must
   Revolt DiscoverP0 Must
 Roles Coloured UsernamesP0 Must
 Users Change Server AvatarP1 Preferred
   Change NicknameP1 Preferred
 SettingsBasic InformationUpdate InformationP1 Preferred
   Update IconP1 Preferred
   Update BannerP1 Preferred
   Update System Message TargetsP1 Preferred
   Update CategoriesP1 Preferred
  RolesCreate RoleP1 Preferred
   List RolesP1 Preferred
   Delete RoleP1 Preferred
   Update Role InformationP1 Preferred
   Update PermissionsP1 Preferred
  CustomisationCreate EmojiP2 Best Effort
   List EmojiP2 Best Effort
   Delete EmojiP2 Best Effort
  UsersList MembersP2 Best Effort
   Set RolesP2 Best Effort
   Create InviteP0 Must
   List InviteP2 Best Effort
   Delete InviteP2 Best Effort
  BansList BansP1 Preferred
   Pardon UserP1 Preferred
  Delete Server P1 Preferred
ChannelsInterfaceChannel InformationView Channel DescriptionP1 Preferred
   Age GateP0 Must
 ServerLeft SidebarServer InformationP0 Must
   View Server DescriptionP1 Preferred
   List ChannelsP0 Must
   Channel CategoriesP0 Must
   Channel IconsP1 Preferred
  Member ListView MembersP0 Must
   Hoisted RolesP0 Must
 Messaging (Text Channel)Read MessagesLoad Recent MessagesP0 Must
   Inline Badges🚧🚧P1 Preferred
   Inline Pronouns🚧Unapplicable
   MasqueradeP1 Preferred
   Show MentionsP0 Must
   Show Channel LinksP0 Must
   Show Server Links🚧P3 Unimportant
   Show Message LinksP3 Unimportant
   Show RepliesP0 Must
   Show ReactionsP0 Must
   Attachments🚧P0 Must
   Embeds🚧P0 Must
   System🚧P1 Preferred
   InvitesP1 Preferred
  Quick ActionsReplyP0 Must
   ReactP1 Preferred
   Copy TextP0 Must
   Copy LinkP0 Must
   Copy IDP0 Must
   Mark as unreadP1 Preferred
   QuoteP3 Unimportant
   EditP0 Must
   DeleteP0 Must
   PinPX New Feature
  Read Chat HistoryLoad Older MessagesP0 Must
   Jump to EndP1 Preferred
   Jump to Message🚧P2 Best Effort
   Search MessagesP2 Best Effort
   View Pinned MessagesPX New Feature
  Message CompositionSend MessagesP0 Must
   Reply to MessagesP0 Must
   Pick EmojiP2 Best Effort
   Pick GIF🚧P3 Unimportant
   Autocomplete ChannelP1 Preferred
   Autocomplete UserP1 Preferred
   Autocomplete EmojiP1 Preferred
   Send FilesP0 Must
   Preview files to sendP1 Preferred
   Show messages being sent🚧P1 Preferred
   Retry sending failed messages🚧P2 Best Effort
   Show attachments being sentP2 Best Effort
   Cancel message being sentP3 Unimportant
 Talking (Voice Channels)Base VoiceLivekit VoicePX New Feature
   Livekit screen sharingPX New Feature
   Livekit facecamPX New Feature
  UX featuresVolume controlPX New Feature
   Ignore blocked usersPX New Feature
   Hide non-video participantsPX New Feature
  ModerationDisconnect membersPX New Feature
   Server mute membersPX New Feature
 SettingsBasic InformationUpdate InformationP2 Best Effort
   Set IconP2 Best Effort
   Edit Role PermissionsP2 Best Effort
   Edit Group PermissionsP2 Best Effort
  WebhooksList WebhooksPX New Feature
   Create WebhookPX New Feature
   Update Webhook InformationPX New Feature
   Delete WebhookPX New Feature
   Copy Webhook URLsPX New Feature
MarkdownRSM Basic StylesP0 Must
   Code BlocksP1 Preferred
   Code FormattingP1 Preferred
   Block QuotesP1 Preferred
   SpoilersP1 Preferred
   LinksP1 Preferred
   HeadingsP2 Best Effort
   TablesP2 Best Effort
   ListsP2 Best Effort
   Text Formatting Extensions🚧P2 Best Effort
   Inline Math (KaTeX)Unapplicable
   Block Math (KaTeX)P2 Best Effort
   TimestampsP2 Best Effort
   Unicode EmojiP1 Preferred
   Custom Emoji🚧P1 Preferred
User SafetyReporting Report MessageP0 Must
   Report ServerP0 Must
   Report UserP0 Must
SettingsUserAccountUpdate UsernameP1 Preferred
   Update EmailP1 Preferred
   Update PasswordP1 Preferred
   Configure MFA RecoveryP2 Best Effort
   Configure MFA TOTPP2 Best Effort
   Disable AccountP0 Must
   Delete AccountP0 Must
  ProfileUpdate AvatarP1 Preferred
   Update BackgroundP1 Preferred
   Update BioP1 Preferred
  SessionsList SessionsP2 Best Effort
   Delete SessionP2 Best Effort
   Log out all other sessionsP2 Best Effort
 ClientAppearanceCustomise ThemeP2 Best Effort
   Customise FontP3 Unimportant
   Customise Emoji PackP3 Unimportant
  NotificationsDesktopP0 Must
   Web PushN/AN/AP0 Must
   Desktop Native PushP3 Unimportant
   Mobile Native PushP0 Must
  Language P2 Best Effort
  Settings Sync P0 Must
  DesktopStart with ComputerN/AN/AP2 Best Effort
   Minimise to TrayN/AN/AP2 Best Effort
 RevoltBotsCreate BotP3 Unimportant
   List BotsP3 Unimportant
   Update InformationP3 Unimportant
   Update IconP3 Unimportant
   Invite to Server / GroupP3 Unimportant
 MiscFeedback Information P1 Preferred
  Changelogs P2 Best Effort
  Source code Unapplicable
  Log out P0 Must

Conversations Sidebar

The conversations sidebar shows a short navigation menu to:

  • Home
  • Friends
  • Saved Notes

With a list of direct and group conversations below (descending order by last message ID with a fallback to channel ID).

The conversations sidebar as it appears in Revolt for Web

Server Sidebar

The server sidebar is composed of the server banner with the title on top, and a list of categories and channels below.

Categories may be collapsed, and continue to show the active channels when collapsed

The server sidebar as it appears in Revolt for Web

Ordered Channels Algorithm

  • Initialise a set of uncategorised channel IDs from server channels the client may access.
  • Initialise an empty list of categories
  • If server.categories are defined, for each category:
    • Remove all the channels defined in the category from set
    • Add category to list
  • If the set is not empty:
    • Find the "default" category if it exists in
    • Merge the "default" category channels if they exist with and preceding the set
    • Create a category with id="default" and add it to the start of the list if it does not exist
  • Return the list

Text Channels

Message Grouping Algorithm

Within this algorithm, tail refers to whether the message omits displaying the user's avatar / username.

This algorithm expects that the rendering of the output list is reversed, if you need it from oldest to newest: either reverse at the end or adapt the logic.

  1. Let L be the list of messages ordered from newest to oldest
  2. Let E be the list of elements to be rendered
  3. For each message M in L:
    1. Let tail be true
    2. Let date be null
    3. Let next be the next item in list L
    4. If next is not null:
      1. Let adate and bdate be the times the message M and the next message were created respectively
      2. If adate and bdate are not the same day:
        1. Let date be adate
      3. Let tail be false if one of the following conditions is satisfied:
        • Message M and last do not have the same author
        • The difference between bdate and adate is equal to or over 7 minutes
        • The masquerades for message M and last do not match
        • Message M or last is a system message
        • Message M replies to one or more messages
    5. Else if next is null:
      1. Let tail be false
    6. Push the message to list E
      (type id: 0; cache key: message id:tail)
    7. If date is not null:
      1. Push date formatted as "MMMM D, YYYY" to list E
        (type id: 1; cache key: formatted date)

Note: the Revolt client also caches the objects produced for list E by pushing each object into a Map by their given cache key above, then retrieving them the next time the code is run OR creating a new object if one is not present. This prevents Solid.js from completely rebuilding the DOM whenever the message list updates.

Session Lifecycle

To ensure reliability for users on Revolt, clients should implement the following rigid specification for maintaining a session. At a high-level, it should be implemented as a state machine.

flowchart TD
subgraph LOGGED_IN[Logged In - Display Client UI]
CONNECTED
CONNECTING
RECONNECTING
DISCONNECTED
OFFLINE
...
end

subgraph LOGGED_OUT[Logged Out - Display Auth Flow or Loading Indicator]
READY
LOGGING_IN
ONBOARDING
DISPOSE
ERROR
end

READY -->|LOGIN_UNCACHED| LOGGING_IN
READY -->|LOGIN_CACHED| CONNECTING
LOGGING_IN -->|NO_USER| ONBOARDING
LOGGING_IN -->|PERMANENT_FAILURE| ERROR
LOGGING_IN -->|TEMPORARY_FAILURE| ERROR
ONBOARDING -->|CANCEL| DISPOSE
DISPOSE -->|READY| READY
ERROR -->|DISMISS| DISPOSE

LOGGING_IN -->|SOCKET_CONNECTED| CONNECTED
RECONNECTING -->|SOCKET_CONNECTED| CONNECTED
CONNECTING -->|SOCKET_CONNECTED| CONNECTED

ONBOARDING -->|USER_CREATED| LOGGING_IN
CONNECTED -->|TEMPORARY_FAILURE| DISCONNECTED
DISCONNECTED -->|RETRY| RECONNECTING
DISCONNECTED -->|DEVICE_OFFLINE| OFFLINE
RECONNECTING -->|TEMPORARY_FAILURE| DISCONNECTED
CONNECTING -->|TEMPORARY_FAILURE| DISCONNECTED
CONNECTING -->|PERMANENT_FAILURE| ERROR
RECONNECTING -->|PERMANENT_FAILURE| ERROR
OFFLINE -->|DEVICE_ONLINE| RECONNECTING

... -->|LOGOUT| DISPOSE

Implementation Details

The table below describes how each node should behave.

  • All nodes SHOULD have corresponding visual feedback.
  • Nodes MAY have effects on entry.
  • You SHOULD keep track of additional state such as:
    • Number of connection failures
NodeUser InterfaceLogic
READYShow login interfaceMay transition out by external source.
LOGGING_INShow loading indicatorOn entry, try to authenticate the user.
ONBOARDINGShow username selectionMay transition out by external source.
ERRORShow the permanent error with option to dismiss itMay transition out by external source.
DISPOSEShow loading indicatorDispose current client and create a new one.
CONNECTINGShow client UI with banner "Connecting"On entry, try to connect socket.
CONNECTEDShow client UIMay transition out by external source.
Set connection failures to .
DISCONNECTEDShow client UI with banner "Disconnected"May transition out by external source.
Increment connection failures by .

If the device is offline, trigger transition to OFFLINE.

On entry, set a timer to retry.
On exit, cancel timer.
RECONNECTINGShow client UI with banner "Reconnecting"On entry, invalidate cached data (message history, members list) and try to connect socket.
OFFLINEShow client UI with banner "Device offline"May transition out by external source.

Please use the formula for the delay where is failure count.

// JavaScript implementation
let retryIn =
  (Math.pow(2, connectionFailureCount) - 1) * (0.8 + Math.random() * 0.4);
let retryInMs = retryIn * 1e3;
setTimeout(() => reconnect(), retryInMs);

The following listeners need to be registered that emit the given transitions:

ListenerTransition
Connected to Revolt (and initial data loaded)SOCKET_CONNECTED
Connection to Revolt droppedSOCKET_DROPPED
Received logout event from socketLOGOUT
Connection failedTEMPORARY_FAILURE
Connection failed (session invalid)PERMANENT_FAILURE
Device has gone onlineDEVICE_ONLINE

Socket Details

When implementing your WebSocket connection, you should also implement the following:

  • A heartbeat mechanism that sends the Ping event every 30 seconds, and disconnects if a Pong event is not received within 10 seconds.
  • A connection timeout mechanism that drops the WebSocket connection if no message is received within 10 seconds of initiating the connection.

User Experience Considerations

  • While not strictly relevant to session lifecycle, if you encounter BlockedByShield during login, you should provide a link to the relevant support article.
  • Upon encountering a permanent error (session invalid), you should use the known user information to fetch their current flags. This way the message can be tailored to display if: they have been logged out, they disabled their account, their account has been suspended, or their account has been banned.
  • When a logout event is received externally, show some sort of indicator that they have been logged out beyond just kicking them to the login screen.
  • If the connection failure count reaches or more, query the health service for any outage information. This point is WIP, need considerations about increasing polling rate while connection failures are high, etc.

Using Form2

Form2 is the primary and recommended way to handle any complex user input to request pipeline.

It is intended to supersede <Form /> with the design goals of making form declarations a bit more involved to avoid complex implicit behaviours from forming, essentially following KISS philosophy.

@todo add information about how to display general errors!

In general, usage looks as follows:

// create your group using solid-forms
const group = createFormGroup({ .. });

// define your handler
// NB. can be a Promise<..>
async function yourSubmitHandler() {
  // do something with group.controls.<control>.value, ..

  if (somethingWrong) {
    throw "error";
    // mapAnyError() is called on this value
    // which is inserted into errors as 'error'
  }
}

// define a handler to reset the form (if you need this functionality)
function onReset() {
  // group.controls.<control>.setValue(/* whatever it should be / now is */)
}

Then create the form itself in the JSX code:

<form onSubmit={Form2.submitHandler(group, yourSubmitHandler, onReset)}>
  // use a wrapper for TextField that implements control:
  <Form2.TextField
    name="name"
    control={group.controls.name}
    label={t("i18n.key")}
  />

  // include an image picker:
  <Form2.FileInput control={group.controls.icon} accept="image/*" />
  
  // use the provided buttons for best integration:
  <Row>
    // if appropriate, allow the user to reset the form back to original state
    <Form2.Reset group={editGroup} onReset={onReset} />
    // in-built submission button:
    <Form2.Submit group={editGroup}>{t("action.save")}</Form2.Submit>
    // you should also indicate when submission is pending:
    <Show when={editGroup.isPending}>
      <CircularProgress />
    </Show>
  </Row>
</form>

There are examples provided throughout the code, just search for <Form2 in the codebase.