2016-06-04 02:39:06 +02:00
|
|
|
Zulip architectural overview
|
|
|
|
============================
|
|
|
|
|
2020-08-11 01:47:54 +02:00
|
|
|
Key codebases
|
2016-06-04 02:39:06 +02:00
|
|
|
-------------
|
|
|
|
|
2019-05-20 20:48:34 +02:00
|
|
|
The main Zulip codebase is at <https://github.com/zulip/zulip>. It
|
|
|
|
contains the Zulip backend (written in Python 3.x and Django), the
|
|
|
|
webapp (written in JavaScript and TypeScript) and our library of
|
2020-06-08 23:04:39 +02:00
|
|
|
incoming webhook [integrations](https://zulip.com/integrations)
|
2019-05-20 20:48:34 +02:00
|
|
|
with other services and applications (see [the directory structure
|
2019-09-30 19:37:56 +02:00
|
|
|
guide](../overview/directory-structure.md)).
|
2016-06-04 02:39:06 +02:00
|
|
|
|
2017-10-20 18:11:56 +02:00
|
|
|
[Zulip Mobile](https://github.com/zulip/zulip-mobile) is the official
|
|
|
|
mobile Zulip client supporting both iOS and Android, written in
|
2019-05-20 20:48:34 +02:00
|
|
|
JavaScript with React Native, and [Zulip
|
|
|
|
Desktop](https://github.com/zulip/zulip-desktop) is the official Zulip
|
2020-05-13 17:55:44 +02:00
|
|
|
desktop client for macOS, Linux, and Windows.
|
|
|
|
[Zulip Terminal](https://github.com/zulip/zulip-terminal) is our
|
|
|
|
official terminal-based client.
|
2017-10-20 18:11:56 +02:00
|
|
|
|
|
|
|
We also maintain several separate repositories for integrations and
|
2019-05-20 20:48:34 +02:00
|
|
|
other glue code: [Python API
|
|
|
|
bindings](https://github.com/zulip/python-zulip-api); [JavaScript API
|
|
|
|
bindings](https://github.com/zulip/zulip-js); a [Hubot
|
|
|
|
adapter](https://github.com/zulip/hubot-zulip); integrations with
|
|
|
|
[Phabricator](https://github.com/zulip/phabricator-to-zulip),
|
2016-06-04 02:39:06 +02:00
|
|
|
[Jenkins](https://github.com/zulip/zulip-jenkins-plugin),
|
|
|
|
[Puppet](https://github.com/matthewbarr/puppet-zulip),
|
|
|
|
[Redmine](https://github.com/zulip/zulip-redmine-plugin), and
|
2019-08-28 11:58:53 +02:00
|
|
|
[Trello](https://github.com/zulip/trello-to-zulip);
|
2019-05-20 20:48:34 +02:00
|
|
|
and [many more](https://github.com/zulip/).
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
We use [Transifex](https://www.transifex.com/zulip/zulip/) to do
|
|
|
|
translations.
|
|
|
|
|
2017-01-15 05:13:22 +01:00
|
|
|
In this overview, we'll mainly discuss the core Zulip server and web
|
2016-06-04 02:39:06 +02:00
|
|
|
application.
|
|
|
|
|
|
|
|
Usage assumptions and concepts
|
|
|
|
------------------------------
|
|
|
|
|
2019-05-20 20:48:34 +02:00
|
|
|
Zulip is a real-time team chat application meant to provide a great
|
|
|
|
experience for a wide range of organizations, from companies to
|
2020-02-07 00:03:10 +01:00
|
|
|
volunteer projects to groups of friends, ranging in size from a small
|
2019-05-20 20:48:34 +02:00
|
|
|
team to 10,000s of users. It has [hundreds of
|
2020-06-08 23:04:39 +02:00
|
|
|
features](https://zulip.com/features) both larger and small, and
|
2019-05-20 20:48:34 +02:00
|
|
|
supports dedicated apps for iOS, Android, Linux, Windows, and macOS,
|
|
|
|
all modern web browsers, several cross-protocol chat clients, and
|
2020-06-08 23:04:39 +02:00
|
|
|
numerous dedicated [Zulip API](https://zulip.com/api) clients
|
2019-05-20 20:48:34 +02:00
|
|
|
(e.g. bots).
|
|
|
|
|
|
|
|
A server can host multiple Zulip *realms* (organizations), each on its
|
2020-06-09 00:58:42 +02:00
|
|
|
own (sub)domain. While most installations host only one organization, some
|
|
|
|
such as zulip.com host thousands. Each organization is a private
|
2019-05-20 20:48:34 +02:00
|
|
|
chamber with its own users, streams, customizations, and so on. This
|
|
|
|
means that one person might be a user of multiple Zulip realms. The
|
|
|
|
administrators of an organization have a great deal of control over
|
|
|
|
who can register an account, what permissions new users have, etc. For
|
|
|
|
more on security considerations and options, see [the security model
|
2019-09-30 19:37:56 +02:00
|
|
|
section](../production/security-model.md) and the [Zulip Help
|
2020-06-08 23:04:39 +02:00
|
|
|
Center](https://zulip.com/help).
|
2016-08-15 00:07:53 +02:00
|
|
|
|
2016-06-04 02:39:06 +02:00
|
|
|
Components
|
|
|
|
----------
|
|
|
|
|
2017-11-08 17:55:36 +01:00
|
|
|
![architecture-simple](../images/architecture_simple.png)
|
2017-01-09 22:29:15 +01:00
|
|
|
|
2017-02-10 10:09:31 +01:00
|
|
|
### Django and Tornado
|
2016-06-04 02:39:06 +02:00
|
|
|
|
2017-02-10 10:09:31 +01:00
|
|
|
Zulip is primarily implemented in the
|
|
|
|
[Django](https://www.djangoproject.com/) Python web framework. We
|
2020-03-27 01:32:21 +01:00
|
|
|
also make use of [Tornado](https://www.tornadoweb.org) for the
|
2017-02-10 10:09:31 +01:00
|
|
|
real-time push system.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
Django is the main web application server; Tornado runs the
|
|
|
|
server-to-client real-time push system. The app servers are configured
|
|
|
|
by the Supervisor configuration (which explains how to start the server
|
|
|
|
processes; see "Supervisor" below) and the nginx configuration (which
|
|
|
|
explains which HTTP requests get sent to which app server).
|
|
|
|
|
2019-01-16 20:25:43 +01:00
|
|
|
Tornado is an asynchronous server and is meant specifically to hold
|
dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013. We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not. Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.
While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.
This WebSockets code path had a lot of downsides/complexity,
including:
* The messy hack involving constructing an emulated request object to
hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
needs and must be provisioned independently from the rest of the
server).
* A duplicate check_send_receive_time Nagios test specific to
WebSockets.
* The requirement for users to have their firewalls/NATs allow
WebSocket connections, and a setting to disable them for networks
where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
been poorly maintained, and periodically throws random JavaScript
exceptions in our production environments without a deep enough
traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
server restart, and especially for large installations like
zulipchat.com, resulting in extra delay before messages can be sent
again.
As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients. We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.
If we later want WebSockets, we’ll likely want to just use Django
Channels.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-07-23 01:43:40 +02:00
|
|
|
open tens of thousands of long-lived (long-polling)
|
2019-01-16 20:25:43 +01:00
|
|
|
connections -- that is to say, routes that maintain a persistent
|
|
|
|
connection from every running client. For this reason, it's
|
|
|
|
responsible for event (message) delivery, but not much else. We try to
|
|
|
|
avoid any blocking calls in Tornado because we don't want to delay
|
|
|
|
delivery to thousands of other connections (as this would make Zulip
|
|
|
|
very much not real-time). For instance, we avoid doing cache or
|
|
|
|
database queries inside the Tornado code paths, since those blocking
|
|
|
|
requests carry a very high performance penalty for a single-threaded,
|
|
|
|
asynchronous server system. (In principle, we could do non-blocking
|
|
|
|
requests to those services, but the Django-based database libraries we
|
|
|
|
use in most of our codebase using don't support that, and in any case,
|
|
|
|
our architecture doesn't require Tornado to do that).
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
The parts that are activated relatively rarely (e.g. when people type or
|
dependencies: Remove WebSockets system for sending messages.
Zulip has had a small use of WebSockets (specifically, for the code
path of sending messages, via the webapp only) since ~2013. We
originally added this use of WebSockets in the hope that the latency
benefits of doing so would allow us to avoid implementing a markdown
local echo; they were not. Further, HTTP/2 may have eliminated the
latency difference we hoped to exploit by using WebSockets in any
case.
While we’d originally imagined using WebSockets for other endpoints,
there was never a good justification for moving more components to the
WebSockets system.
This WebSockets code path had a lot of downsides/complexity,
including:
* The messy hack involving constructing an emulated request object to
hook into doing Django requests.
* The `message_senders` queue processor system, which increases RAM
needs and must be provisioned independently from the rest of the
server).
* A duplicate check_send_receive_time Nagios test specific to
WebSockets.
* The requirement for users to have their firewalls/NATs allow
WebSocket connections, and a setting to disable them for networks
where WebSockets don’t work.
* Dependencies on the SockJS family of libraries, which has at times
been poorly maintained, and periodically throws random JavaScript
exceptions in our production environments without a deep enough
traceback to effectively investigate.
* A total of about 1600 lines of our code related to the feature.
* Increased load on the Tornado system, especially around a Zulip
server restart, and especially for large installations like
zulipchat.com, resulting in extra delay before messages can be sent
again.
As detailed in
https://github.com/zulip/zulip/pull/12862#issuecomment-536152397, it
appears that removing WebSockets moderately increases the time it
takes for the `send_message` API query to return from the server, but
does not significantly change the time between when a message is sent
and when it is received by clients. We don’t understand the reason
for that change (suggesting the possibility of a measurement error),
and even if it is a real change, we consider that potential small
latency regression to be acceptable.
If we later want WebSockets, we’ll likely want to just use Django
Channels.
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2019-07-23 01:43:40 +02:00
|
|
|
click on something) are processed by the Django application server.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
2017-02-10 10:09:31 +01:00
|
|
|
There is detailed documentation on the
|
2019-09-30 19:37:56 +02:00
|
|
|
[real-time push and event queue system](../subsystems/events-system.md); most of
|
2017-02-10 10:09:31 +01:00
|
|
|
the code is in `zerver/tornado`.
|
|
|
|
|
2017-06-15 20:47:47 +02:00
|
|
|
#### HTML templates, JavaScript, etc.
|
|
|
|
|
|
|
|
Zulip's HTML is primarily implemented using two types of HTML
|
|
|
|
templates: backend templates (powered by the [Jinja2][] template
|
|
|
|
engine used for logged-out ("portico") pages and the webapp's base
|
|
|
|
content) and frontend templates (powered by [Handlebars][]) used for
|
|
|
|
live-rendering HTML from JavaScript for things like the main message
|
|
|
|
feed.
|
|
|
|
|
|
|
|
For more details on the frontend, see our documentation on
|
2019-09-30 19:37:56 +02:00
|
|
|
[translation](../translating/translating.md),
|
2019-10-17 00:42:54 +02:00
|
|
|
[templates](../subsystems/html-css.html#html-templates),
|
2019-09-30 19:37:56 +02:00
|
|
|
[directory structure](../overview/directory-structure.md), and
|
2019-10-23 18:23:25 +02:00
|
|
|
[the static asset pipeline](../subsystems/html-css.html#static-asset-pipeline).
|
2017-06-15 20:47:47 +02:00
|
|
|
|
|
|
|
[Jinja2]: http://jinja.pocoo.org/
|
2020-03-21 18:17:19 +01:00
|
|
|
[Handlebars]: https://handlebarsjs.com/
|
2017-06-15 20:47:47 +02:00
|
|
|
|
2016-06-04 02:39:06 +02:00
|
|
|
### nginx
|
|
|
|
|
|
|
|
nginx is the front-end web server to all Zulip traffic; it serves static
|
|
|
|
assets and proxies to Django and Tornado. It handles HTTP requests
|
|
|
|
according to the rules laid down in the many config files found in
|
|
|
|
`zulip/puppet/zulip/files/nginx/`.
|
|
|
|
|
|
|
|
`zulip/puppet/zulip/files/nginx/zulip-include-frontend/app` is the most
|
|
|
|
important of these files. It explains what happens when requests come in
|
|
|
|
from outside.
|
|
|
|
|
|
|
|
- In production, all requests to URLs beginning with `/static/` are
|
|
|
|
served from the corresponding files in `/home/zulip/prod-static/`,
|
|
|
|
and the production build process (`tools/build-release-tarball`)
|
|
|
|
compiles, minifies, and installs the static assets into the
|
|
|
|
`prod-static/` tree form. In development, files are served directly
|
2020-10-23 02:43:28 +02:00
|
|
|
from `/static/` in the Git repository.
|
2020-08-06 22:07:33 +02:00
|
|
|
- Requests to `/json/events` and `/api/v1/events`, i.e. the
|
|
|
|
real-time push system, are sent to the Tornado server.
|
|
|
|
- Requests to all other paths are sent to the Django app running via
|
|
|
|
`uWSGI` via `unix:/home/zulip/deployments/uwsgi-socket`.
|
2017-06-15 20:38:38 +02:00
|
|
|
- By default (i.e. if `LOCAL_UPLOADS_DIR` is set), nginx will serve
|
|
|
|
user-uploaded content like avatars, custom emoji, and uploaded
|
|
|
|
files. However, one can configure Zulip to store these in a cloud
|
|
|
|
storage service like Amazon S3 instead.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
2020-05-13 17:59:22 +02:00
|
|
|
Note that we do not use `nginx` in the development environment, opting
|
|
|
|
for a simple Tornado-based proxy instead.
|
|
|
|
|
2016-06-04 02:39:06 +02:00
|
|
|
### Supervisor
|
|
|
|
|
|
|
|
We use [supervisord](http://supervisord.org/) to start server processes,
|
|
|
|
restart them automatically if they crash, and direct logging.
|
|
|
|
|
|
|
|
The config file is
|
2017-05-06 10:03:15 +02:00
|
|
|
`zulip/puppet/zulip/templates/supervisor/zulip.conf.template.erb`. This
|
|
|
|
is where Tornado and Django are set up, as well as a number of background
|
2016-06-04 02:39:06 +02:00
|
|
|
processes that process event queues. We use event queues for the kinds
|
|
|
|
of tasks that are best run in the background because they are
|
|
|
|
expensive (in terms of performance) and don't have to be synchronous
|
2016-07-27 04:00:29 +02:00
|
|
|
--- e.g., sending emails or updating analytics. Also see [the queuing
|
2019-09-30 19:37:56 +02:00
|
|
|
guide](../subsystems/queuing.md).
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
### memcached
|
|
|
|
|
2018-07-31 23:07:42 +02:00
|
|
|
memcached is used to cache database model
|
|
|
|
objects. `zerver/lib/cache.py` and `zerver/lib/cache_helpers.py`
|
|
|
|
manage putting things into memcached, and invalidating the cache when
|
|
|
|
values change. The memcached configuration is in
|
|
|
|
`puppet/zulip/files/memcached.conf`. See our
|
2019-09-30 19:37:56 +02:00
|
|
|
[caching guide](../subsystems/caching.md) to learn how this works in
|
2018-07-31 23:07:42 +02:00
|
|
|
detail.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
### Redis
|
|
|
|
|
2020-02-07 00:03:10 +01:00
|
|
|
Redis is used for a few very short-term data stores, primarily
|
|
|
|
our rate-limiting system.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
Redis is configured in `zulip/puppet/zulip/files/redis` and it's a
|
|
|
|
pretty standard configuration except for the last line, which turns off
|
|
|
|
persistence:
|
|
|
|
|
|
|
|
# Zulip-specific configuration: disable saving to disk.
|
|
|
|
save ""
|
|
|
|
|
2020-10-23 02:43:28 +02:00
|
|
|
People often wonder if we could replace memcached with Redis (or
|
|
|
|
replace RabbitMQ with Redis, with some loss of functionality).
|
2019-03-18 20:25:11 +01:00
|
|
|
|
|
|
|
The answer is likely yes, but it wouldn't improve Zulip.
|
|
|
|
Operationally, our current setup is likely easier to develop and run
|
2020-10-23 02:43:28 +02:00
|
|
|
in production than a pure Redis system would be. Meanwhile, the
|
|
|
|
perceived benefit for using Redis is usually to reduce memory
|
2019-03-18 20:25:11 +01:00
|
|
|
consumption by running fewer services, and no such benefit would
|
|
|
|
materialize:
|
|
|
|
|
|
|
|
* Our cache uses significant memory, but that memory usage would be
|
2020-10-23 02:43:28 +02:00
|
|
|
essentially the same with Redis as it is with memcached.
|
2019-03-18 20:25:11 +01:00
|
|
|
* All of these services have low minimum memory requirements, and in
|
2020-10-23 02:43:28 +02:00
|
|
|
fact our applications for Redis and RabbitMQ do not use significant
|
2019-03-18 20:25:11 +01:00
|
|
|
memory even at scale.
|
2020-10-23 02:43:28 +02:00
|
|
|
* We would likely need to run multiple Redis services (with different
|
2019-03-18 20:25:11 +01:00
|
|
|
configurations) in order to ensure the pure LRU use case (memcached)
|
|
|
|
doesn't push out data that we want to persist until expiry
|
2020-10-23 02:43:28 +02:00
|
|
|
(Redis-based rate limiting) or until consumed (RabbitMQ-based
|
2019-03-18 20:25:11 +01:00
|
|
|
queuing of deferred work).
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
### RabbitMQ
|
|
|
|
|
|
|
|
RabbitMQ is a queueing system. Its config files live in
|
|
|
|
`zulip/puppet/zulip/files/rabbitmq`. Initial configuration happens in
|
|
|
|
`zulip/scripts/setup/configure-rabbitmq`.
|
|
|
|
|
|
|
|
We use RabbitMQ for queuing expensive work (e.g. sending emails
|
|
|
|
triggered by a message, push notifications, some analytics, etc.) that
|
|
|
|
require reliable delivery but which we don't want to do on the main
|
|
|
|
thread. It's also used for communication between the application server
|
|
|
|
and the Tornado push system.
|
|
|
|
|
|
|
|
Two simple wrappers around `pika` (the Python RabbitMQ client) are in
|
2016-11-27 06:56:06 +01:00
|
|
|
`zulip/zerver/lib/queue.py`. There's an asynchronous client for use in
|
2017-02-10 10:09:31 +01:00
|
|
|
Tornado and a more general client for use elsewhere. Most of the
|
|
|
|
processes started by Supervisor are queue processors that continually
|
|
|
|
pull things out of a RabbitMQ queue and handle them; they are defined
|
|
|
|
in `zerver/worker/queue_processors.py`.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
2019-09-30 19:37:56 +02:00
|
|
|
Also see [the queuing guide](../subsystems/queuing.md).
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
### PostgreSQL
|
|
|
|
|
|
|
|
PostgreSQL (also known as Postgres) is the database that stores all
|
|
|
|
persistent data, that is, data that's expected to live beyond a user's
|
2020-06-27 01:26:57 +02:00
|
|
|
current session. Starting with Zulip 3.0, new Zulip installations
|
|
|
|
will install modern Postgres release rather than using the version included
|
|
|
|
with the operating system.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
In production, Postgres is installed with a default configuration. The
|
|
|
|
directory that would contain configuration files
|
|
|
|
(`puppet/zulip/files/postgresql`) has only a utility script and a custom
|
2020-10-23 02:43:28 +02:00
|
|
|
list of stopwords used by a PostgreSQL extension.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
2020-10-23 02:43:28 +02:00
|
|
|
In a development environment, configuration of that PostgreSQL
|
2016-06-27 23:50:38 +02:00
|
|
|
extension is handled by `tools/postgres-init-dev-db` (invoked by
|
2017-01-14 11:19:26 +01:00
|
|
|
`tools/provision`). That file also manages setting up the
|
2020-10-23 02:43:28 +02:00
|
|
|
development PostgreSQL user.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
2020-04-21 22:03:12 +02:00
|
|
|
`tools/provision` also invokes `tools/rebuild-dev-database`
|
2016-06-27 23:50:38 +02:00
|
|
|
to create the actual database with its schema.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
2018-07-30 22:16:26 +02:00
|
|
|
### Thumbor and thumbnailing
|
|
|
|
|
|
|
|
We use Thumbor, a popular open source thumbnailing server, to serve
|
|
|
|
images (both for inline URL previews and serving uploaded image
|
2019-09-30 19:37:56 +02:00
|
|
|
files). See [our thumbnailing docs](../subsystems/thumbnailing.md)
|
2018-07-30 22:16:26 +02:00
|
|
|
for more details on how this works.
|
|
|
|
|
2016-06-04 02:39:06 +02:00
|
|
|
### Nagios
|
|
|
|
|
|
|
|
Nagios is an optional component used for notifications to the system
|
|
|
|
administrator, e.g., in case of outages.
|
|
|
|
|
|
|
|
`zulip/puppet/zulip/manifests/nagios.pp` installs Nagios plugins from
|
2016-10-14 15:28:02 +02:00
|
|
|
`puppet/zulip/files/nagios_plugins/`.
|
2016-06-04 02:39:06 +02:00
|
|
|
|
|
|
|
This component is intended to install Nagios plugins intended to be run
|
|
|
|
on a Nagios server; most of the Zulip Nagios plugins are intended to be
|
|
|
|
run on the Zulip servers themselves, and are included with the relevant
|
|
|
|
component of the Zulip server (e.g.
|
2020-10-20 04:10:17 +02:00
|
|
|
`puppet/zulip/manifests/postgresql_backups.pp` installs a few under
|
2020-07-14 02:53:23 +02:00
|
|
|
`/usr/lib/nagios/plugins/zulip_backups`).
|
2016-10-12 01:43:23 +02:00
|
|
|
|
|
|
|
## Glossary
|
|
|
|
|
|
|
|
This section gives names for some of the elements in the Zulip UI used
|
2019-05-20 20:35:47 +02:00
|
|
|
in Zulip development conversations. In general, our goal is to
|
|
|
|
minimize the set of terminology listed here by giving elements
|
|
|
|
self-explanatory names.
|
2016-10-12 01:43:23 +02:00
|
|
|
|
2020-05-13 18:01:49 +02:00
|
|
|
* **bankruptcy**: When a user has been off Zulip for several days and
|
|
|
|
has hundreds of unread messages, they are prompted for whether
|
|
|
|
they want to mark all their unread messages as read. This is
|
|
|
|
called "declaring bankruptcy" (in reference to the concept in
|
|
|
|
finance).
|
|
|
|
|
2016-10-12 01:43:23 +02:00
|
|
|
* **chevron**: A small downward-facing arrow next to a message's
|
|
|
|
timestamp, offering contextual options, e.g., "Reply", "Mute [this
|
|
|
|
topic]", or "Link to this conversation". To avoid visual clutter,
|
|
|
|
the chevron only appears in the web UI upon hover.
|
|
|
|
|
2020-10-23 02:43:28 +02:00
|
|
|
* **ellipsis**: A small vertical three dot icon (technically called
|
2020-06-16 05:19:18 +02:00
|
|
|
as ellipsis-v), present in sidebars as a menu icon.
|
2020-10-23 02:43:28 +02:00
|
|
|
It offers contextual options for global filters (All messages
|
|
|
|
and Starred messages), stream filters and topics in left
|
|
|
|
sidebar and users in right sidebar. To avoid visual clutter
|
2020-06-16 05:19:18 +02:00
|
|
|
ellipsis only appears in the web UI upon hover.
|
|
|
|
|
2017-03-22 23:49:02 +01:00
|
|
|
* **huddle**: What the codebase calls a "group private message".
|
|
|
|
|
2016-10-12 01:43:23 +02:00
|
|
|
* **message editing**: If the realm admin allows it, then after a user
|
|
|
|
posts a message, the user has a few minutes to click "Edit" and
|
|
|
|
change the content of their message. If they do, Zulip adds a
|
|
|
|
marker such as "(EDITED)" at the top of the message, visible to
|
|
|
|
anyone who can see the message.
|
|
|
|
|
2017-03-22 23:49:02 +01:00
|
|
|
* **realm**: What the codebase calls an "organization" in the UI.
|
|
|
|
|
2016-10-12 01:43:23 +02:00
|
|
|
* **recipient bar**: A visual indication of the context of a message
|
|
|
|
or group of messages, displaying the stream and topic or private
|
|
|
|
message recipient list, at the top of a group of messages. A
|
|
|
|
typical 1-line message to a new recipient shows to the user as
|
|
|
|
three lines of content: first the recipient bar, second the
|
|
|
|
sender's name and avatar alongside the timestamp (and, on hover,
|
|
|
|
the star and the chevron), and third the message content. The
|
|
|
|
recipient bar is or contains hyperlinks to help the user narrow.
|
|
|
|
|
|
|
|
* **star**: Zulip allows a user to mark any message they can see,
|
|
|
|
public or private, as "starred". A user can easily access messages
|
2017-08-08 07:37:39 +02:00
|
|
|
they've starred through the "Starred messages" link in the
|
|
|
|
left sidebar, or use "is:starred" as a narrow or a search
|
2016-10-12 01:43:23 +02:00
|
|
|
constraint. Whether a user has or has not starred a particular
|
|
|
|
message is private; other users and realm admins don't know
|
|
|
|
whether a message has been starred, or by whom.
|
2017-03-22 23:09:41 +01:00
|
|
|
|
2017-03-22 23:49:02 +01:00
|
|
|
* **subject**: What the codebase calls a "topic" in many places.
|