Merge branch 'old_master' into travis/msc/inline-widgets

travis/msc/inline-widgets
Travis Ralston 2 years ago
commit 2a26597c50

1
.gitignore vendored

@ -1,5 +1,4 @@
/api/node_modules
/assets
/assets.tar.gz
/data/msc
/env*

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

@ -0,0 +1 @@
Extend `/_matrix/client/r0/login` to accept a `m.login.appservice`, as per [MSC2778](https://github.com/matrix-org/matrix-doc/pull/2778).

@ -12,10 +12,6 @@ theme = ["docsy"]
disableKinds = ["taxonomy", "taxonomyTerm"]
# Change the default for assets, because the old Python toolchain uses "assets" for build output.
# When the old toolchain is retired we can switch back to the default here.
assetDir = "assets-hugo"
[languages]
[languages.en]
title = "Matrix Specification"

@ -312,10 +312,11 @@ information.
The homeserver needs to give the application service *full control* over
its namespace, both for users and for room aliases. This means that the
AS should be able to create/edit/delete any room alias in its namespace,
as well as create/delete any user in its namespace. No additional API
AS should be able to manage any users and room alias in its namespace. No additional API
changes need to be made in order for control of room aliases to be
granted to the AS. Creation of users needs API changes in order to:
granted to the AS.
Creation of users needs API changes in order to:
- Work around captchas.
- Have a 'passwordless' user.
@ -334,8 +335,26 @@ user ID without a password.
username: "_irc_example"
}
Similarly, logging in as users needs API changes in order to allow the AS to
log in without needing the user's password. This is achieved by including the
`as_token` on a `/login` request, along with a login type of
`m.login.application_service`.
POST /_matrix/client/%CLIENT_MAJOR_VERSION%/login
Authorization: Bearer YourApplicationServiceTokenHere
Content:
{
type: "m.login.application_service",
"identifier": {
"type": "m.id.user",
"user": "_irc_example"
}
}
Application services which attempt to create users or aliases *outside*
of their defined namespaces will receive an error code `M_EXCLUSIVE`.
of their defined namespaces, or log in as users outside of their defined
namespaces will receive an error code `M_EXCLUSIVE`.
Similarly, normal users who attempt to create users or aliases *inside*
an application service-defined namespace will receive the same
`M_EXCLUSIVE` error code, but only if the application service has

@ -1024,6 +1024,41 @@ client supports it, the client should redirect the user to the
is complete, the client will need to submit a `/login` request matching
`m.login.token`.
#### Appservice Login
An appservice can log in by providing a valid appservice token and a user within the appservice's
namespace.
{{% boxes/note %}}
Appservices do not need to log in as individual users in all cases, as they
can perform [Identity Assertion](/application-service-api#identity-assertion)
using the appservice token. However, if the appservice needs a scoped token
for a single user then they can use this API instead.
{{% /boxes/note %}}
This request must be authenticated by the [appservice `as_token`](/application-service-api#registration)
(see [Client Authentication](#client-authentication) on how to provide the token).
To use this login type, clients should submit a `/login` request as follows:
```json
{
"type": "m.login.appservice",
"identifier": {
"type": "m.id.user",
"user": "<user_id or user localpart>"
}
}
```
If the access token is not valid, does not correspond to an appservice
or the user has not previously been registered then the homeserver will
respond with an errcode of `M_FORBIDDEN`.
If the access token does correspond to an appservice, but the user id does
not lie within its namespace then the homeserver will respond with an
errcode of `M_EXCLUSIVE`.
{{% http-api spec="client-server" api="login" %}}
{{% http-api spec="client-server" api="logout" %}}

@ -0,0 +1,151 @@
# MSC2285: Private read receipts
Currently users must send read receipts in order to affect their notification
counts, which alerts other people that the user has read their message. For
primarily privacy reasons, it may be desirable to users to not advertise to
others that they've read a message.
## Proposal
This MSC proposes adding a new `receiptType` (see [the receipts
spec](https://spec.matrix.org/v1.3/client-server-api/#receipts)) of
`m.read.private`. This `receiptType` is used when the user wants to affect their
notification count but doesn't want other users to see their read receipt.
To move the user's private read receipt to `$123` the client can make a POST
request to the [`/receipt`
endpoint](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3roomsroomidreceiptreceipttypeeventid).
For example:
```HTTP
POST /_matrix/client/v3/rooms/!a:example.org/receipt/m.read.private/$123
{}
```
The MSC also proposes adding `m.fully_read` and `m.read.private` as a possible
`receiptType` for `/receipt` to make this endpoint consistent with
`/read_markers`. (we have two endpoints that do essentially the same thing, so
it would make sense for them to be consistent)
Alternatively, the client can move the user's `m.fully_read` marker and/or
`m.read` receipt at the same time as `m.read.private` by making a POST request
to the [`/read_markers`
endpoint](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3roomsroomidread_markers).
For example:
```HTTP
POST /_matrix/client/r0/rooms/!a:example.org/read_markers
{
"m.fully_read": "$123",
"m.read": "$123",
"m.read.private": "$123"
}
```
Both `m.read` and `m.read.private` clear notifications in the same way. If the
user sent two receipts into a room, the later one should be the one that decides
the notification count.
The receipt that is more "ahead" of the other takes precedence when considering
notifications and a client's rendering of read receipts. This means that given
an ordered set of events A, B, C, and D the public read receipt could be at
point C, private at point A. If the user moves the private receipt from A to B
then the user's notification count is still considered from point C as the public
receipt is further ahead, still. Other users would also see the user's public read
receipt as not having moved. The user can then move the private read receipt
to point D, hopping over the public receipt, to change their notification count.
For clarity, if the public receipt is "fast forwarded" to be at the same position
as the private receipt then the public receipt is broadcast to other users, even
if previously considered private.
Note that like regular read receipts today, neither receipt can cause a backwards
movement: both receipts can only move forwards, but do not have to be ahead of
each other. It's valid to, for example, update a public read receipt which lags
20 messages behind the private one.
The `m.fully_read` property is now optional for the [`/read_markers`
endpoint](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3roomsroomidread_markers)
as sometimes we only want to send `m.read.private`.
The MSC proposes that from now on, not all things sent over `/receipt` are
federated. Servers MUST NOT send receipts of `receiptType` `m.read.private` to
any other user than the sender. Servers also MUST NOT send receipts of
`receiptType` `m.read.private` to any server over federation.
## Security considerations
Servers could act as if `m.read.private` is the same as `m.read` so the user
must already trust the homeserver to a degree however, and the methods of
notifying the user to the problem are difficult to implement. Users can always
run their own homeservers to ensure it behaves correctly.
## Potential issues
Clients which support read receipts would end up rendering the user's receipt as
jumping down when they send a message. This is no different from how IRC and
similarly bridged users are perceived today.
## Alternatives
It has been suggested to use account data to store the setting that controls
whether read receipts should be private on a per-account/per-room basis. While
this might have some benefits, it is much less flexible.
Previous iterations of this MSC additionally suggested that having an `m.hidden`
flag on existing read receipts could work, however this feels like assigning too
much responsibility to an existing structure.
## Unstable prefix
While this MSC is not considered stable, implementations should use
`org.matrix.msc2285` as a namespace.
|Stable (post-FCP)|Unstable |
|-----------------|---------------------------------|
|`m.read.private` |`org.matrix.msc2285.read.private`|
Clients should check for server support before sending private read receipts:
if the server does not support them, then a private read receipt will not clear
any notifications for the user.
The presence of `org.matrix.msc2285` or `org.matrix.msc2285.stable` in
`unstable_features` is a reliable indication that a server supports private read
receipts; however the converse is not true: their absence does not necessarily
mean that the server does *not* support private read receipts. In particular,
the server may have been updated to a future spec version which includes
private read receipts, and hence removed the `unstable_features` entry.
Therefore, if a client has this feature enabled, but the server does not advertise
support for this MSC in `unstable_features`, the client should either keep sending
private read receipts with the risk that notifications will not be clearing, or it
should warn the user and start sending public read receipts instead.
To mitigate this problem, once this MSC gets merged and once it becomes a part of a
spec version, clients should update their implementations as fast as possible to
accommodate the fact that the way of detecting server support will change: clients
will now be looking for that spec version in `/versions`.
### While the MSC is unstable
During this period, to detect server support clients should check for the
presence of the `org.matrix.msc2285` flag in `unstable_features` on `/versions`.
Clients are also required to use the unstable prefixes (see [unstable
prefix](#unstable-prefix)) during this time.
### Once the MSC is merged but not in a spec version
Once this MSC is merged, but is not yet part of the spec, clients should rely on
the presence of the `org.matrix.msc2285.stable` flag in `unstable_features` to
determine server support. If the flag is present, clients are required to use
stable prefixes (see [unstable prefix](#unstable-prefix)).
### Once the MSC is in a spec version
Once this MSC becomes a part of a spec version, clients should rely on the
presence of the spec version, that supports the MSC, in `versions` on
`/versions`, to determine support. Servers are encouraged to keep the
`org.matrix.msc2285.stable` flag around for a reasonable amount of time
to help smooth over the transition for clients. "Reasonable" is intentionally
left as an implementation detail, however the MSC process currently recommends
*at most* 2 months from the date of spec release.

@ -0,0 +1,250 @@
# MSC2674: Event relationships
It's common to want to send events in Matrix which relate to existing events -
for instance, reactions, edits and even replies/threads.
This proposal is one in a series of proposals that defines a mechanism for
events to relate to each other. Together, these proposals replace
[MSC1849](https://github.com/matrix-org/matrix-doc/pull/1849).
* This proposal defines a standard shape for indicating events which relate to
other events.
* [MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675) defines APIs to
let the server calculate the aggregations on behalf of the client, and so
bundle the related events with the original event where appropriate.
* [MSC2676](https://github.com/matrix-org/matrix-doc/pull/2676) defines how
users can edit messages using this mechanism.
* [MSC2677](https://github.com/matrix-org/matrix-doc/pull/2677) defines how
users can annotate events, such as reacting to events with emoji, using this
mechanism.
* [MSC3267](https://github.com/matrix-org/matrix-doc/pull/3267) defines how events
can make a reference to other events.
* [MSC3389](https://github.com/matrix-org/matrix-doc/pull/3389) defines changes to
the redaction algorithm, to preserve the type and target id of a relation.
## Proposal
This proposal introduces the concept of relations, which can be used to
associate new information with an existing event.
A relationship is an object with a field `rel_type`, which is a string describing the type of relation,
and a field `event_id`, which is a string that represents the event_id of the target event of this relation.
The target event must exist in the same room as the relating event is sent.
Both of those fields are required. An event is said to contain a relationship if its `content` contains
a relationship with all the required fields under the `m.relates_to` key. If any of these conditions is not met,
clients and servers should treat the event as if it does not contain a relationship.
Servers should reject events not meeting these conditions with an HTTP 400 error when
they are received via the client-server API.
Here's a (partial) example of an event relating to another event:
```json
{
"content": {
"m.relates_to": {
"rel_type": "m.replace",
"event_id": "$abc:server.tld"
}
}
}
```
All the information about the relationship lives under the `m.relates_to` key.
If it helps, you can think of relations as a "subject verb object" triple,
where the subject is the relation event itself; the verb is the `rel_type`
field of the `m.relates_to` and the object is the `event_id` field.
We consciously do not support multiple different relations within a single event,
in order to keep the API simple. This means that if event A relates to event B
in two different ways you would send two events to describe the two relations,
rather than bundling them into a single event. Another MSC,
like [MSC 3051](https://github.com/matrix-org/matrix-doc/pull/3051),
can propose a change to add support for multiple relations if it turns out that
this would facilitate certain use cases.
Relations do not yet replace the
[reply mechanism currently defined in the spec](https://matrix.org/docs/spec/client_server/r0.6.1#rich-replies).
### Relation types
Any values for `rel_type` should abide the
[general guidelines for identifiers](https://github.com/matrix-org/matrix-doc/pull/3171).
The `rel_type` property determines how an event relates to another and can be used
by clients to determine how and in what context a relation should be displayed.
[MSC 2675](https://github.com/matrix-org/matrix-doc/pull/2675) proposes to also interpret the `rel_type` server-side.
It is left up to the discretion of other MSCs building on this one whether they introduce
`rel_type`s that are specific to their use case or that can serve a broad range
of use cases. MSCs may define additional properties on the relation object for a given `rel_type`.
Currently, a few `rel_type`s are already proposed. Here's a non-exhaustive list:
- `m.replace` in [MSC 2676](https://github.com/matrix-org/matrix-doc/pull/2676).
- `m.annotation` in [MSC 2677](https://github.com/matrix-org/matrix-doc/pull/2677).
- `m.reference` in [MSC 3267](https://github.com/matrix-org/matrix-doc/pull/3267).
- `m.thread` in [MSC 3440](https://github.com/matrix-org/matrix-doc/pull/3440).
### Sending relations
Related events are normal Matrix events, and can be sent by the normal `/send`
API.
The server should postprocess relations if needed before sending them into a
room, as defined by the relationship type. For example, a relationship type
might only allow a user to send one related message to a given event.
### Receiving relations
Relations are received like other non-state events, with `/sync`,
`/messages` and `/context`, as normal discrete Matrix events. As explained
in the limitations, clients may be unaware of some relations using just these endpoints.
[MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675) defines ways in
which the server may aid clients in processing relations by aggregating the
events.
### Redactions
Events with a relation may be redacted like any other event.
[MSC3389](https://github.com/matrix-org/matrix-doc/pull/3389) proposes that
the redaction algorithm should preserve the type and target id of a relation.
However, event relationships can still be used in existing room versions, but
the user experience may be worse if redactions are performed.
## Potential issues
### Federation considerations
We have a problem with resynchronising relations after a gap in federation:
We have no way of knowing that an edit happened in the gap to one of the events
we already have. So, we'll show inconsistent data until we backfill the gap.
* We could write this off as a limitation.
* Or we could make *ALL* relations a DAG, so we can spot holes at the next
relation, and go walk the DAG to pull in the missing relations? Then, the
next relation for an event could pull in any of the missing relations.
Socially this probably doesn't work as reactions will likely drop off over
time, so by the time your server comes back there won't be any more reactions
pulling the missing ones in.
* Could we also ask the server, after a gap, to provide all the relations which
happened during the gap to events whose root preceeded the gap.
* "Give me all relations which happened between this set of
forward-extremities when I lost sync, and the point i've rejoined the DAG,
for events which preceeded the gap"?
* Would be hard to auth all the relations which this api coughed up.
* We could auth them based only the auth events of the relation, except we
lose the context of the nearby DAG which we'd have if it was a normal
backfilled event.
* As a result it would be easier for a server to retrospectively lie about
events on behalf of its users.
* This probably isn't the end of the world, plus it's more likely to be
consistent than if we leave a gap.
* i.e. it's better to consistent with a small chance of being maliciously
wrong, than inconsistent with a guaranteed chance of being innocently
wrong.
* We'd need to worry about pagination.
* This is probably the best solution, but can also be added as a v2.
* In practice this seems to not be an issue, which is worth complicating
the s-s API over. Clients very rarely jump over the federation gap to an edit.
In most cases they scroll up, which backfills the server and we have all the
edits, when we reach the event before the gap.
## Limitations
Based solely on this MSC, relations are only received as discrete events in
the timeline, so clients may only have an incomplete image of all the relations
with an event if they do not fill gaps (syncs with a since token that have
`limited: true` set in the sync response for a room) in the timeline.
In practice, this has proven not to be too big of a problem, as reactions
(as proposed in [MSC 2677](https://github.com/matrix-org/matrix-doc/pull/2677))
tend to be posted close after the target event in the timeline.
A more complete solution to this has been deferred to
[MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675).
## Tradeoffs
### Event shape
Shape of
```json
"content": {
"m.relates_to": {
"m.reference": {
"event_id": "$another:event.com"
}
}
}
```
versus
```json
"content": {
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "$another:event.com"
}
}
```
The reasons to go with `rel_type` is:
* This format is now in use in the wider matrix ecosystem without a prefix,
in spite of the original MSC 1849 not being merged. This situation is not ideal
but we still don't want to break compatibility with several clients.
* We don't need the extra indirection to let multiple relations apply to a given pair of
events, as that should be expressed as separate relation events.
* If we want 'adverbs' to apply to 'verbs' in the subject-verb-object triples which
relations form, then we apply it as mixins to the relation data itself rather than trying
to construct subject-verb-verb-object sentences.
* We decided to not adopt the format used by `m.in_reply_to` as it allows for multiple relations
and is hence overly flexible. Also, the relation type of `m.in_reply_to` is also overly specific
judged by the guidelines for `rel_type`s laid out in this MSC. Having replies use the same
format as relations is postponed to a later MSC, but it would likely involve replies
adopting the relation format with a more broadly useful `rel_type` (possibly the `m.reference`
type proposed in [MSC3267](https://github.com/matrix-org/matrix-doc/pull/3267)),
rather than relations adopting the replies format.
## Historical context
pik's MSC441 has:
Define the JSON schema for the aggregation event, so the server can work out
which fields should be aggregated.
```json
"type": "m.room._aggregation.emoticon",
"content": {
"emoticon": "::smile::",
"msgtype": "?",
"target_id": "$another:event.com"
}
```
These would then be aggregated, based on target_id, and returned as annotations on
the source event in an `aggregation_data` field:
```json
"content": {
...
"aggregation_data": {
"m.room._aggregation.emoticon": {
"aggregation_data": [
{
"emoticon": "::smile::",
"event_id": "$14796538949JTYis:pik-test",
"sender": "@pik:pik-test"
}
],
"latest_event_id": "$14796538949JTYis:pik-test"
}
}
}
```

@ -0,0 +1,320 @@
# MSC2675: Serverside aggregations of message relationships
It's common to want to send events in Matrix which relate to existing events -
for instance, reactions, edits and even replies/threads.
Clients typically need to track the related events alongside the original
event they relate to, in order to correctly display them. For instance,
reaction events need to be aggregated together by summing and be shown next to
the event they react to; edits need to be aggregated together by replacing the
original event and subsequent edits, etc.
It is possible to treat relations as normal events and aggregate them
clientside, but to do so comprehensively could be very resource intensive, as
the client would need to spider all possible events in a room to find
relationships and maintain a correct view.
Instead, this proposal seeks to solve this problem by defining APIs to let the
server calculate the aggregations on behalf of the client, and so bundle the
aggregated data with the original event where appropriate. It also proposes an
API to let clients paginate through all relations of an event.
This proposal is one in a series of proposals that defines a mechanism for
events to relate to each other. Together, these proposals replace
[MSC1849](https://github.com/matrix-org/matrix-doc/pull/1849).
* [MSC2674](https://github.com/matrix-org/matrix-doc/pull/2674) defines a
standard shape for indicating events which relate to other events.
* This proposal defines APIs to let the server calculate the aggregations on
behalf of the client, and so bundle the aggregated data with the original
event where appropriate.
* [MSC2676](https://github.com/matrix-org/matrix-doc/pull/2676) defines how
users can edit messages using this mechanism.
* [MSC2677](https://github.com/matrix-org/matrix-doc/pull/2677) defines how
users can annotate events, such as reacting to events with emoji, using this
mechanism.
## Proposal
### Aggregations
Relation events can be aggregated per relation type by the server.
The format of the aggregated value (hereafter called "aggregation")
depends on the relation type.
Some relation types might group the aggregations by the `key` property
in the relation and aggregate to an array, while
others might aggregate to a single object or any other value really.
Here are some non-normative examples of what aggregations can look like:
Example aggregation for [`m.thread`](https://github.com/matrix-org/matrix-doc/pull/3440) (which
aggregates all relations into a single object):
```
{
"latest_event": {
"content": { ... },
...
},
"count": 7,
"current_user_participated": true
}
```
Example aggregation for [`m.annotation`](https://github.com/matrix-org/matrix-doc/pull/2677) (which
aggregates relations into a list of objects, grouped by `key`).
```
[
{
"key": "👍",
"origin_server_ts": 1562763768320,
"count": 3
},
{
"key": "👎",
"origin_server_ts": 1562763768320,
"count": 2
}
]
```
#### Bundling
Other than during non-gappy incremental syncs, timeline events that have other
events relate to them should include the aggregation of those related events
in the `m.relations` property of their unsigned data. This process is
referred to as "bundling", and the aggregated relations included via
this mechanism are called "bundled aggregations".
By sending a summary of the relations, bundling
avoids us having to always send lots of individual relation events
to the client.
Aggregations are never bundled into state events. This is a current
implementation detail that could be revisited later,
rather than a specific design decision.
The following client-server APIs should bundle aggregations
with events they return:
- `GET /rooms/{roomId}/messages`
- `GET /rooms/{roomId}/context/{eventId}`
- `GET /rooms/{roomId}/event/{eventId}`
- `GET /sync`, only for room sections in the response where `limited` field
is `true`; this amounts to all rooms in the response if
the `since` request parameter was not passed, also known as an initial sync.
- `GET /relations`, as proposed in this MSC.
Deprecated APIs like `/initialSync` and `/events/{eventId}` are *not* required
to bundle aggregations.
The bundled aggregations are grouped according to their relation type.
The format of `m.relations` (here with *non-normative* examples of
the [`m.replace`](https://github.com/matrix-org/matrix-doc/pull/2676) and
[`m.annotation`](https://github.com/matrix-org/matrix-doc/pull/2677) relation
types) is as follows:
```json
{
"event_id": "abc",
"unsigned": {
"m.relations": {
"m.annotation": {
"key": "👍",
"origin_server_ts": 1562763768320,
"count": 3
},
"m.replace": {
"event_id": "$edit_event_id",
"origin_server_ts": 1562763768320,
"sender": "@alice:localhost"
},
}
}
}
```
#### Client-side aggregation
Bundled aggregations on an event give a snapshot of what relations were known
at the time the event was received. When relations are received through `/sync`,
clients should locally aggregate (as they might have done already before
supporting this MSC) the relation on top of any bundled aggregation the server
might have sent along previously with the target event, to get an up to date
view of the aggregations for the target event. The aggregation algorithm is the
same as the one described here for the server.
### Querying relations
A single event can have lots of associated relations, and we do not want to
overload the client by, for example, including them all bundled with the
related-to event. Instead, we also provide a new `/relations` API in
order to paginate over the relations, which behaves in a similar way to
`/messages`, except using `next_batch` and `prev_batch` names
(in line with `/sync` API). Tokens from `/sync` or `/messages` can be
passed to `/relations` to only get relating events from a section of
the timeline.
The `/relations` API returns the discrete relation events
associated with an event that the server is aware of
in standard topological order. Note that events may be missing,
see [limitations](#servers-might-not-be-aware-of-all-relations-of-an-event).
You can optionally filter by a given relation type and the event type of the
relating event:
```
GET /_matrix/client/v1/rooms/{roomID}/relations/{event_id}[/{rel_type}[/{event_type}]][?from=token][&to=token][&limit=amount]
```
```
{
"chunk": [
{
"type": "m.reaction",
"sender": "...",
"content": {
"m.relates_to": {
"rel_type": "m.annotation",
...
}
}
}
],
"prev_batch": "some_token",
"next_batch": "some_token",
}
```
The endpoint does not have any trailing slashes. It requires authentication
and is not rate-limited.
The `from` and `limit` query parameters are used for pagination, and work
just like described for the `/messages` endpoint.
Note that [MSC2676](https://github.com/matrix-org/matrix-doc/pull/2676)
adds the related-to event in `original_event` property of the response.
This way the full history (e.g. also the first, original event) of the event
is obtained without further requests. See that MSC for further details.
### End to end encryption
Since the server has to be able to aggregate relation events, structural
information about relations must be visible to the server, and so the
`m.relates_to` field must be included in the plaintext.
A future MSC may define a method for encrypting certain parts of the
`m.relates_to` field that may contain sensitive information.
### Redactions
Redacted relations should not be taken into consideration in
bundled aggregations, nor should they be returned from `/relations`.
Requesting `/relations` on a redacted event should
still return any existing relation events.
This is in line with other APIs like `/context` and `/messages`.
### Local echo
For the best possible user experience, clients should also include unsent
relations into the client-side aggregation. When adding a relation to the send
queue, clients should locally aggregate it into the relations of the target
event, ideally regardless of the target event having received an `event_id`
already or still being pending. If the client gives up on sending the relation
for some reason, the relation should be de-aggregated from the relations of
the target event. If the client offers the user a possibility of manually
retrying to send the relation, it should be re-aggregated when the user does so.
De-aggregating a relation refers to rerunning the aggregation for a given
target event while not considering the de-aggregated event any more.
Upon receiving the remote echo for any relations, a client is likely to remove
the pending event from the send queue. Here, it should also de-aggregate the
pending event from the target event's relations, and re-aggregate the received
remote event from `/sync` to make sure the client-side aggregation happens with
the same event data as on the server.
When adding a redaction for a relation to the send queue, the relation
referred to should be de-aggregated from the relations of the target of the
relation. Similar to a relation, when the sending of the redaction fails or
is cancelled, the relation should be aggregated again.
If the target event is still pending and hasn't received its `event_id` yet,
clients can locally relate relation events to their target by using
`transaction_id` like they already do for detecting remote echos when sending
events.
## Edge cases
### How do you handle ignored users?
* Information about relations sent from ignored users must never be sent to
the client, either in aggregations or discrete relation events.
This is to let you block someone from harassing you with emoji reactions
(or using edits as a side-channel to harass you). Therefore, it is possible
that different users will see different aggregations (a different last edit,
or a different reaction count) on an event.
## Limitations
### Relations can be missed while not being in the room
Relation events behave no different from other events in terms of room history visibility,
which means that some relations might not be visible to a user while they are not invited
or have not joined the room. This can cause a user to see an incomplete edit history or reaction count
based on discrete relation events upon (re)joining a room.
Ideally the server would not include these events in aggregations, as it would mean breaking
the room history visibility rules, but this MSC defers addressing this limitation and
specifying the exact server behaviour to [MSC3570](https://github.com/matrix-org/matrix-doc/pull/3570).
### Servers might not be aware of all relations of an event
The response of `/relations` might be incomplete because the homeserver
potentially doesn't have the full DAG of the room. The federation API doens't
have an equivalent of the `/relations` API, so has no way but to fetch the
full DAG over federation to assure itself that it is aware of all relations.
[MSC2836](https://github.com/matrix-org/matrix-doc/pull/2836) provided a proposal for following relationships over federation in the "Querying relationships over federation" section via a `/_matrix/federation/v1/event_relationships` API
### Event type based aggregation and filtering won't work well in encrypted rooms
The `/relations` endpoint allows filtering by event type,
which for encrypted rooms will be `m.room.encrypted`, rendering this filtering
less useful for encrypted rooms. Aggregation algorithms that take the type of
the relating events they aggregate into account will suffer from the same
limitation.
## Future extensions
### Handling limited (gappy) syncs
For the special case of a gappy incremental sync, many relations (particularly
reactions) may have occurred during the gap. It would be inefficient to send
each one individually to the client, but it would also be inefficient to send
all possible bundled aggregations to the client.
The server could tell the client the event IDs of events which
predate the gap which received relations during the gap. This means that the
client could invalidate its copy of those events (if any) and then requery them
(including their bundled relations) from the server if/when needed,
for example using an extension of the `/event` API for batch requests.
The server could do this with a new `stale_events` field of each room object
in the sync response. The `stale_events` field would list all the event IDs
prior to the gap which had updated relations during the gap. The event IDs
would be grouped by relation type,
and paginated as per the normal Matrix pagination model.
This was originally part of this MSC but left out to limit the scope
to what is implemented at the time of writing.
## Prefix
While this MSC is not considered stable, the endpoints become:
- `GET /_matrix/client/unstable/rooms/{roomID}/relations/{eventID}[/{relationType}[/{eventType}]]`
None of the newly introduced identifiers should use a prefix though, as this MSC
tries to document relation support already being used in
the wider matrix ecosystem.

@ -0,0 +1,407 @@
# MSC2676: Message editing
Users may wish to edit previously sent messages, for example to correct typos.
This can be done by sending a new message with an indication that it replaces
the previously sent message.
This proposal is one in a series of proposals that defines a mechanism for
events to relate to each other. Together, these proposals replace
[MSC1849](https://github.com/matrix-org/matrix-doc/pull/1849).
* [MSC2674](https://github.com/matrix-org/matrix-doc/pull/2674) defines a
standard shape for indicating events which relate to other events.
* [MSC2675](https://github.com/matrix-org/matrix-doc/pull/2675) defines APIs to
let the server calculate the aggregations on behalf of the client, and so
bundle the related events with the original event where appropriate.
* This proposal defines how users can edit messages using this mechanism.
* [MSC2677](https://github.com/matrix-org/matrix-doc/pull/2677) defines how
users can annotate events, such as reacting to events with emoji, using this
mechanism.
## Background
Element-Web (then Riot-Web) and Synapse both implemented initial support for
message editing, following the proposals of MSC1849, in May 2019
([matrix-react-sdk](https://github.com/matrix-org/matrix-react-sdk/pull/2952),
[synapse](https://github.com/matrix-org/synapse/pull/5209)). Element-Android
and Element-iOS also added implementations around that time. Unfortunately,
those implementations presented the feature as "production-ready", despite it
not yet having been adopted into the Matrix specification.
The current situation is therefore that client or server implementations hoping
to interact with Element users must simply follow the examples of that
implementation. In other words, message edits form part of the *de-facto* spec
despite not being formalised in the written spec. This is clearly a regrettable
situation. Hopefully, processes have improved over the last three years so that
this situation will not arise again. Nevertheless there is little we can do
now other than formalise the status quo.
This MSC, along with the others mentioned above, therefore seeks primarily to
do that. Although there is plenty of scope for improvement, we consider that
better done in *future* MSCs, based on a shared understanding of the *current*
implementation.
In short, this MSC prefers fidelity to the current implementations over
elegance of design.
## Proposal
### `m.replace` event relationship type
A new `rel_type` of `m.replace` is defined for use with the `m.relates_to`
field as defined in
[MSC2674](https://github.com/matrix-org/matrix-doc/pull/2674). This is
intended primarily for handling edits, and lets you define an event which
replaces an existing event.
Such an event, with `rel_type: m.replace`, is referred to as a "message edit event".
### `m.new_content` property
The `content` of a message edit event must contain a `m.new_content` property
which defines the replacement content. (This allows the normal `body` fields to
be used for a fallback for clients who do not understand replacement events.)
For instance, an `m.room.message` which replaces an existing event might look like:
```json
{
"type": "m.room.message",
"content": {
"body": "* Hello! My name is bar",
"msgtype": "m.text",
"m.new_content": {
"body": "Hello! My name is bar",
"msgtype": "m.text"
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": "$some_event_id"
}
}
}
```
The `m.new_content` can include any properties that would normally be found in
an event's `content` property, such as `formatted_body`.
#### Encrypted events
If the original event was encrypted, the replacement should be too. In that
case, `m.new_content` is placed in the `content` of the encrypted payload. The
`m.relates_to` property remains unencrypted, as required by the
[relationships](https://spec.matrix.org/v1.3/client-server-api/#forming-relationships-between-events)
section of the Client-Server API specification.
For example, an encrypted replacement event might look like this:
```json
{
"type": "m.room.encrypted",
"content": {
"m.relates_to": {
"rel_type": "m.replace",
"event_id": "$some_event_id"
},
"algorithm": "m.megolm.v1.aes-sha2",
"sender_key": "<sender_curve25519_key>",
"device_id": "<sender_device_id>",
"session_id": "<outbound_group_session_id>",
"ciphertext": "<encrypted_payload_base_64>"
}
}
```
... and, once decrypted, the payload might look like this:
```json
{
"type": "m.room.<event_type>",
"room_id": "!some_room_id",
"content": {
"body": "* Hello! My name is bar",
"msgtype": "m.text",
"m.new_content": {
"body": "Hello! My name is bar",
"msgtype": "m.text"
}
}
}
```
Note that:
* There is no `m.relates_to` property in the encrypted payload. (Any such
property would be ignored.)
* There is no `m.new_content` property in the cleartext `content` of the
`m.room.encrypted` event. (Again, any such property would be ignored.)
For clarity: the payload must be encrypted as normal, ratcheting the Megolm session
as normal. The original Megolm ratchet entry should **not** be re-used.
#### Applying `m.new_content`
When applying a replacement, the `content` property of the original event is
replaced entirely by the `m.new_content`, with the exception of `m.relates_to`,
which is left *unchanged*. Any `m.relates_to` property within `m.new_content`
is ignored.
For example, given a pair of events:
```json
{
"event_id": "$original_event",
"type": "m.room.message",
"content": {
"body": "I *really* like cake",
"msgtype": "m.text",
"formatted_body": "I <em>really</em> like cake",
}
}
```
```json
{
"event_id": "$edit_event",
"type": "m.room.message",
"content": {
"body": "* I *really* like *chocolate* cake",
"msgtype": "m.text",
"m.new_content": {
"body": "I *really* like *chocolate* cake",
"msgtype": "m.text",
"com.example.extension_property": "chocolate"
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": "$original_event_id"
}
}
}
```
... then the end result is an event as shown below. Note that `formatted_body`
is now absent, because it was absent in the replacement event, but
`m.relates_to` remains unchanged (ie, absent).
```json
{
"event_id": "$original_event",
"type": "m.room.message",
"content": {
"body": "I *really* like *chocolate* cake",
"msgtype": "m.text",
"com.example.extension_property": "chocolate"
}
}
```
Note that the `msgtype` property of `m.room.message` events need not be the
same as in the original event. For example, if a user intended to send a
message beginning with "/me", but their client sends an `m.emote` event
instead, they could edit the message to send be an `m.text` event as they had
originally intended.
### Validity of message edit events
Some message edit events are defined to be invalid. To be considered valid, all
of the following criteria must be satisfied:
* The replacement and original events must have the same `type`.
* Neither the replacement nor original events can be state events (ie, neither
may have a `state_key`).
* The original event must not, itself, have a `rel_type` of `m.replace`.
* The original event and replacement event must have the same `sender`.
* The replacement event (once decrypted, if appropriate) must have an
`m.new_content` property.
The original event and replacement event must also have the same `room_id`, as
required by the
[relationships](https://spec.matrix.org/v1.3/client-server-api/#forming-relationships-between-events)
section of the Client-Server API specification.
If any of these criteria are not satisfied, implementations should ignore the
replacement event (the content of the original should not be replaced, and the
edit should not be included in the server-side aggregation).
### Server behaviour
#### Server-side aggregation of `m.replace` relationships
Note that there can be multiple events with an `m.replace` relationship to a
given event (for example, if an event is edited multiple times). These should
be [aggregated](https://spec.matrix.org/v1.3/client-server-api/#aggregations)
by the homeserver.
The format of the aggregation for `m.replace` simply gives gives the
`event_id`, `origin_server_ts`, and `sender` of the most recent replacement
event (as determined by `origin_server_ts`, falling back to a lexicographic
ordering of `event_id`).
This aggregation is bundled into the `unsigned/m.relations` property of any
event that is the target of an `m.replace` relationship. For example:
```json5
{
"event_id": "$original_event_id",
// ...
"unsigned": {
"m.relations": {
"m.replace": {
"event_id": "$latest_edit_event_id",
"origin_server_ts": 1649772304313,
"sender": "@editing_user:localhost"
}
}
}
}
```
If the original event is redacted, any `m.replace` relationship should **not**
be bundled with it (whether or not any subsequent edits are themselves
redacted). Note that this behaviour is specific to the `m.replace`
relationship.
#### Server-side replacement of content
Whenever an `m.replace` is to be bundled with an event as above, the server should
also modify the `content` of the original event according
to the `m.new_content` of the most recent edit (determined as above).
An exception applies to [`GET
/_matrix/client/v3/rooms/{roomId}/event/{eventId}`](https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv3roomsroomideventeventid),
which should return the *unmodified* event (though the relationship should
still be bundled, as described above).
The endpoints where this behaviour takes place is the same as those where
aggregations are bundled, with the exception of
`/room/{roomId}/event/{eventId}`. This includes:
* `GET /rooms/{roomId}/messages`
* `GET /rooms/{roomId}/context/{eventId}`
* `GET /rooms/{roomId}/relations/{eventId}`
* `GET /rooms/{roomId}/relations/{eventId}/{relType}`
* `GET /rooms/{roomId}/relations/{eventId}/{relType}/{eventType}`
* `GET /sync` when the relevant section has a `limited` value of `true`
* `POST /search` for any matching events under `room_events`.
### Client behaviour
Clients can often ignore message edit events, since any events the server
returns via the C-S API will be updated by the server to account for subsequent
edits.
However, clients should apply the replacement themselves when the server is
unable to do so. This happens in the following situations:
1. The client has already received and stored the original event before the message
edit event arrives.
2. The original event (and hence its replacement) are encrypted.
Client authors are reminded to take note of the requirements for [Validity of
message edit events](#validity-of-message-edit-events), and to ignore any
invalid edit events that may be received.
### Permalinks
Permalinks to edited events should capture the event ID that the creator of the
permalink is viewing at that point (which might be a message edit event).
The client viewing the permalink should resolve this ID to the original event
ID, and then display the most recent version of that event.
### Redactions
When a message using a `rel_type` of `m.replace` is redacted, it removes that
edit revision. This has little effect if there were subsequent edits, however
if it was the most recent edit, the event is in effect reverted to its content
before the redacted edit.
Redacting the original message in effect removes the message, including all
subsequent edits, from the visible timeline. In this situation, homeservers
will return an empty `content` for the original event as with any other
redacted event. It must be noted that, although they are not immediately
visible in Element, subsequent edits remain unredacted and can be seen via API
calls. See [Future considerations](#future-considerations).
### Edits of replies
Some particular constraints apply to events which replace a
[reply](https://spec.matrix.org/v1.3/client-server-api/#rich-replies). In
particular:
* There should be no `m.in_reply_to` property in the the `m.relates_to`
object, since it would be redundant (see [Applying
`m.new_content`](#applying-mnew_content) above, which notes that the original
event's `m.relates_to` is preserved), as well as being contrary to the
spirit of
[MSC2674](https://github.com/matrix-org/matrix-spec-proposals/pull/2674)
which expects only one relationship per event.
* `m.new_content` should **not** contain any ["reply
fallback"](https://spec.matrix.org/v1.3/client-server-api/#fallbacks-for-rich-replies),
since it is assumed that any client which can handle edits can also
display replies natively.
An example of an edit to a reply is as follows:
```json
{
"type": "m.room.message",
"content": {
"body": "> <@richvdh:sw1v.org> ab\n\n * ef",
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"formatted_body": "<mx-reply><blockquote><a href=\"https://matrix.to/#/!qOZKfwKPirAoSosXrf:matrix.org/$1652807718765kOVDf:sw1v.org?via=matrix.org&amp;via=sw1v.org\">In reply to</a> <a href=\"https://matrix.to/#/@richvdh:sw1v.org\">@richvdh:sw1v.org</a><br>ab</blockquote></mx-reply> * ef",
"m.new_content": {
"body": "ef",
"msgtype": "m.text",
"format": "org.matrix.custom.html",
"formatted_body": "ef"
},
"m.relates_to": {
"rel_type": "m.replace",
"event_id": "$original_reply_event"
}
}
}
```
## Future considerations
### Ordering of edits
In future we may wish to consider ordering replacements (or relations in
general) via a DAG rather than using `origin_server_ts` to determine ordering -
particularly to mitigate potential abuse of edits applied by moderators.
Whatever, care must be taken by the server to ensure that if there are multiple
replacement events, the server must consistently choose the same one as all
other servers.
### Redaction of edits
It is highly unintuitive that redacting the original event leaves subsequent
edits visible to curious eyes even though they are hidden from the
timeline. This is considered a bug which this MSC makes no attempt to
resolve. See also
[element-web#11978](https://github.com/vector-im/element-web/issues/11978) and
[synapse#5594](https://github.com/matrix-org/synapse/issues/5594).
### Edits to state events
There are various issues which would need to be resolved before edits to state
events could be supported. In particular, we would need to consider how the
semantically-meaningful fields of the content of a state event relate to
`m.new_content`. Variation between implementations could easily lead to
security problems (See
[element-web#21851](https://github.com/vector-im/element-web/issues/21851) for
example.)
### Editing other users' events
There is a usecase for users with sufficient power-level to edit other peoples'
events. For now, no attempt is made to support this. If it is supported in the
future, we would need to find a way to make it clear in the timeline.

@ -0,0 +1,28 @@
# MSC2832: Homeserver -> Application Service authorization header
Most of the auth tokens in the spec are passed in the `Authorization` header,
with the `access_token` query parameter supported for backwards-compatibility.
For some reason, the application service spec was not updated in the same way
and it still requires using the archaic query parameter when the homeserver
pushes transactions to the appservice.
## Proposal
The `access_token` query parameter is removed from all requests made by the
homeserver to appservice and is replaced with the `Authorization` header with
`Bearer <token>` as the value.
### Backwards-compatibility
Homeservers which want to support old spec versions in the appservice API may
send both the query parameter and header. Similarly, appservices may accept the
token from either source.
## Security considerations
Not fixing this causes access tokens to be logged in many bridges.
## Alternatives
We could add a way for appservices to explicitly specify which spec version
they want in order to implement backwards-compatibility without sending both
tokens.
## Unstable prefix
The authorization header is already used in the client-server spec, and an
unstable prefix would just unnecessarily complicate things.

@ -0,0 +1,389 @@
# MSC2946: Spaces Summary
This MSC depends on [MSC1772](https://github.com/matrix-org/matrix-doc/pull/1772), which
describes why a Space is useful:
> Collecting rooms together into groups is useful for a number of purposes. Examples include:
>
> * Allowing users to discover different rooms related to a particular topic: for example "official matrix.org rooms".
> * Allowing administrators to manage permissions across a number of rooms: for example "a new employee has joined my company and needs access to all of our rooms".
> * Letting users classify their rooms: for example, separating "work" from "personal" rooms.
>
> We refer to such collections of rooms as "spaces".
This MSC attempts to solve how a member of a space discovers rooms in that space. This
is useful for quickly exposing a user to many aspects of an entire community, using the
examples above, joining the "official matrix.org rooms" space might suggest joining a few
rooms:
* A room to discuss development of the Matrix Spec.
* An announcements room for news related to matrix.org.
* An off-topic room for members of the space.
## Proposal
A new client-server API (and corresponding server-server API) is added which allows
for querying for the rooms and spaces contained within a space. This allows a client
to efficiently display a hierarchy of rooms to a user (i.e. without having
to walk the full state of each room).
### Client-server API
An endpoint is provided to walk the space tree, starting at the provided room ID
("the root room"), and visiting other rooms/spaces found via `m.space.child`
events. It recurses into the children and into their children, etc.
Any child room that the user is joined or is potentially joinable (per
[MSC3173](https://github.com/matrix-org/matrix-doc/pull/3173)) is included in
the response. When a room with a `type` of `m.space` is found, it is searched
for valid `m.space.child` events to recurse into.
In order to provide a consistent experience, the space tree should be walked in
a depth-first manner, e.g. whenever a space is found it should be recursed into
by sorting the children rooms and iterating through them.
There could be loops in the returned child events; clients and servers should
handle this gracefully. Similarly, note that a child room might appear multiple
times (e.g. also be a grandchild). Clients and servers should handle this
appropriately.
This endpoint requires authentication and is subject to rate-limiting.
#### Request format
```text
GET /_matrix/client/v1/rooms/{roomID}/hierarchy
```
Query Parameters:
* **`suggested_only`**: Optional. If `true`, return only child events and rooms
where the `m.space.child` event has `suggested: true`. Must be a boolean,
defaults to `false`.
This applies transitively, i.e. if a `suggested_only` is `true` and a space is
not suggested then it should not be searched for children. The inverse is also
true, if a space is suggested, but a child of that space is not then the child
should not be included.
* **`limit`**: Optional: a client-defined limit to the maximum
number of rooms to return per page. Must an integer greater than zero.
Server implementations should impose a maximum value to avoid resource
exhaustion.
* **`max_depth`**: Optional: The maximum depth in the tree (from the root room)
to return. The deepest depth returned will not include children events. Defaults
to no-limit. Must be a non-negative integer.
Server implementations may wish to impose a maximum value to avoid resource
exhaustion.
* **`from`**: Optional. Pagination token given to retrieve the next set of rooms.
Note that if a pagination token is provided, then the parameters given for
`suggested_only` and `max_depth` must be the same.
#### Response Format
* **`rooms`**: `[object]` For each room/space, starting with the root room, a
summary of that room. The fields are the same as those returned by
`/publicRooms` (see
[spec](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-publicrooms)),
with the addition of:
* **`room_type`**: the value of the `m.type` field from the room's
`m.room.create` event, if any.
* **`children_state`**: The stripped state of the `m.space.child` events of
the room per [MSC3173](https://github.com/matrix-org/matrix-doc/pull/3173).
In addition to the standard stripped state fields, the following is included:
* **`origin_server_ts`**: `integer`. The `origin_server_ts` field from the
room's `m.space.child` event. This is required for sorting of rooms as
specified below.
* **`next_batch`**: Optional `string`. The token to supply in the `from` param
of the next `/hierarchy` request in order to request more rooms. If this is absent,
there are no more results.
#### Example request:
```text
GET /_matrix/client/v1/rooms/%21ol19s%3Ableecker.street/hierarchy?
limit=30&
suggested_only=true&
max_depth=4
```
#### Example response:
```jsonc
{
"rooms": [
{
"room_id": "!ol19s:bleecker.street",
"avatar_url": "mxc://bleecker.street/CHEDDARandBRIE",
"guest_can_join": false,
"name": "CHEESE",
"num_joined_members": 37,
"topic": "Tasty tasty cheese",
"world_readable": true,
"join_rules": "public",
"room_type": "m.space",
"children_state": [
{
"type": "m.space.child",
"state_key": "!efgh:example.com",
"content": {
"via": ["example.com"],
"suggested": true
},
"room_id": "!ol19s:bleecker.street",
"sender": "@alice:bleecker.street",
"origin_server_ts": 1432735824653
},
{ ... }
]
},
{ ... }
],
"next_batch": "abcdef"
}
```
#### Errors:
An HTTP response with a status code of 403 and an error code of `M_FORBIDDEN`
should be returned if the user doesn't have permission to view/peek the root room.
This should also be returned if that room does not exist, which matches the
behavior of other room endpoints (e.g.
[`/_matrix/client/r0/rooms/{roomID}/aliases`](https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-rooms-roomid-aliases))
to not divulge that a room exists which the user doesn't have permission to view.
An HTTP response with a status code of 400 and an error code of `M_INVALID_PARAM`
should be returned if the `from` token provided is unknown to the server or if
the `suggested_only` or `max_depth` parameters are modified during pagination.
#### Server behaviour
The server should generate the response as discussed above, by doing a depth-first
search (starting at the "root" room) for any `m.space.child` events. Any
`m.space.child` with an invalid `via` are discarded (invalid is defined as in
[MSC1772](https://github.com/matrix-org/matrix-doc/pull/1772): missing, not an
array or an empty array).
In the case of the homeserver not having access to the state of a room, the
server-server API (see below) can be used to query for this information over
federation from one of the servers provided in the `via` key of the
`m.space.child` event. It is recommended to cache the federation response for a
period of time. The federation results may contain information on a room
that the requesting server is already participating in; the requesting server
should use its local data for such rooms rather than the data returned over
federation.
When the current response page is full, the current state should be persisted
and a pagination token should be generated (if there is more data to return).
To prevent resource exhaustion, the server may expire persisted data that it
deems to be stale.
The persisted state will include:
* The processed rooms.
* Rooms to process (in depth-first order with rooms at the same depth
ordered [according to MSC1772, as updated to below](#msc1772-ordering)).
* Room information from federation responses for rooms which have yet to be
processed.
### Server-server API
The Server-Server API has a similar interface to the Client-Server API, but a
simplified response. It is used when a homeserver is not participating in a room
(and cannot summarize room due to not having the state).
The main difference is that it does *not* recurse into spaces and does not support
pagination. This is somewhat equivalent to a Client-Server request with a `max_depth=1`.
Additional federation requests are made to recurse into sub-spaces. This allows
for trivially caching responses for a short period of time (since it is not
easily known the room summary might have changed).
Since the server-server API does not know the requesting user, the response should
divulge information based on if any member of the requesting server could join
the room. The requesting server is trusted to properly filter this information
using the `world_readable`, `join_rules`, and `allowed_room_ids` fields from the
response.
If the target server is not a member of some children rooms (so would have to send
another request over federation to inspect them), no attempt is made to recurse
into them. They are simply omitted from the `children` key of the response.
(Although they will still appear in the `children_state`key of the `room`.)
Similarly, if a server-set limit on the size of the response is reached, additional
rooms are not added to the response and can be queried individually.
#### Request format
```text
GET /_matrix/federation/v1/hierarchy/{roomID}
```
Query Parameters:
* **`suggested_only`**: The same as the Client-Server API.
#### Response format
The response format is similar to the Client-Server API:
* **`room`**: `object` The summary of the requested room, see below for details.
* **`children`**: `[object]` For each room/space, a summary of that room, see
below for details.
* **`inaccessible_children`**: Optional `[string]`. A list of room IDs which are
children of the requested room, but are inaccessible to the requesting server.
Assuming the target server is non-malicious and well-behaved, then other
non-malicious servers should respond with the same set of inaccessible rooms.
Thus the requesting server can consider the rooms inaccessible from everywhere.
This is used to differentiate between rooms which the requesting server does
not have access to from those that the target server cannot include in the
response (which will simply be missing in the response).
For both the `room` and `children` fields the summary of the room/space includes
the fields returned by `/publicRooms` (see [spec](https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-publicrooms)),
with the addition of:
* **`room_type`**: the value of the `m.type` field from the room's `m.room.create`
event, if any.
* **`allowed_room_ids`**: A list of room IDs which give access to this room per
[MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083).<sup id="a1">[1](#f1)</sup>
#### Example request:
```jsonc
GET /_matrix/federation/v1/hierarchy/{roomID}?
suggested_only=true
```
#### Errors:
An HTTP response with a status code of 404 and an error code of `M_NOT_FOUND` is
returned if the target server is not a member of the requested room or the
requesting server is not allowed to access the room.
### MSC1772 Ordering
[MSC1772](https://github.com/matrix-org/matrix-doc/pull/1772) defines the ordering
of "default ordering of siblings in the room list" using the `order` key:
> Rooms are sorted based on a lexicographic ordering of the Unicode codepoints
> of the characters in `order` values. Rooms with no `order` come last, in
> ascending numeric order of the `origin_server_ts` of their `m.room.create`
> events, or ascending lexicographic order of their `room_id`s in case of equal
> `origin_server_ts`. `order`s which are not strings, or do not consist solely
> of ascii characters in the range `\x20` (space) to `\x7F` (~), or consist of
> more than 50 characters, are forbidden and the field should be ignored if
> received.
Unfortunately there are situations when a homeserver comes across a reference to
a child room that is unknown to it and must decide the ordering. Without being
able to see the `m.room.create` event (which it might not have permission to see)
no proper ordering can be given.
Consider the following case of a space with 3 child rooms:
```
Space A
|
+--------+--------+
| | |
Room B Room C Room D
```
HS1 has users in Space A, Room B, and Room C, while HS2 has users in Room D. HS1 has no users
in Room D (and thus has no state from it). Room B, C, and D do not have an
`order` field set (and default to using the ordering rules above).
When a user asks HS1 for the space summary with a `limit` equal to `2` it cannot
fulfill this request since it is unsure how to order Room B, Room C, and Room D,
but it can only return 2 of them. It *can* reach out over federation to HS2 and
request a space summary for Room D, but this is undesirable:
* HS1 might not have the permissions to know any of the state of Room D, so might
receive a 404 error.
* If we expand the example above to many rooms than this becomes expensive to
query a remote server simply for ordering.
This proposes changing the ordering rules from MSC1772 to the following:
> Rooms are sorted based on a lexicographic ordering of the Unicode codepoints
> of the characters in `order` values. Rooms with no `order` come last, in
> ascending numeric order of the `origin_server_ts` of their `m.space.child`
> events, or ascending lexicographic order of their `room_id`s in case of equal
> `origin_server_ts`. `order`s which are not strings, or do not consist solely
> of ascii characters in the range `\x20` (space) to `\x7E` (~), or consist of
> more than 50 characters, are forbidden and the field should be ignored if
> received.
This modifies the clause for calculating the order to use the `origin_server_ts`
of the `m.space.child` event instead of the `m.room.create` event. This allows
for a defined sorting of siblings based purely on the information available in
the state of the space while still allowing for a natural ordering due to the
age of the relationship.
## Potential issues
A large flat space (a single room with many `m.space.child` events) could cause
a large federation response.
Room version upgrades of rooms in a space are unsolved and left to a future MSC.
When upgrading a room it is unclear if the old room should be removed (in which
case users who have not yet joined the new room will no longer see it in the space)
or leave the old room (in which case users who have joined the new room will see
both). The current recommendation is for clients de-duplicate rooms which are
known old versions of rooms in the space.
## Alternatives
Peeking to explore the room state could be used to build the tree of rooms/spaces,
but this would be significantly more expensive for both clients and servers. It
would also require peeking over federation (which is explored in
[MSC2444](https://github.com/matrix-org/matrix-doc/pull/2444)).
## Security considerations
A space with many sub-spaces and rooms on different homeservers could cause
a large number of federation requests. A carefully crafted space with inadequate
server enforced limits could be used in a denial of service attack. Generally
this is mitigated by enforcing server limits and caching of responses.
The requesting server over federation is trusted to filter the response for the
requesting user. The alternative, where the requesting server sends the requesting
`user_id`, and the target server does the filtering, is unattractive because it
rules out a caching of the result. This does not decrease security since a server
could lie and make a request on behalf of a user in the proper space to see the
given information. I.e. the calling server must be trusted anyway.
## Unstable prefix
During development of this feature it will be available at unstable endpoints.
The client-server API will be:
`/_matrix/client/unstable/org.matrix.msc2946/rooms/{roomID}/hierarchy`
The server-server API will be:
`/_matrix/federation/unstable/org.matrix.msc2946/hierarchy/{roomID}`
## Footnotes
<a id="f1"/>[1]: As a worked example, in the context of
[MSC3083](https://github.com/matrix-org/matrix-doc/pull/3083), consider that Alice
and Bob share a server; Alice is a member of a space, but Bob is not. A remote
server will not know whether the request is on behalf of Alice or Bob (and hence
whether it should share details of restricted rooms within that space).
Consider if the space is modified to include a restricted room on a different server
which allows access from the space. When summarizing the space, the homeserver must make
a request over federation for information on the room. The response should include
the room (since Alice is able to join it). Without additional information the
calling server does not know *why* they received the room and cannot properly
filter the returned results.
Note that there are still potential situations where each server individually
doesn't have enough information to properly return the full summary, but these
do not seem reasonable in what is considered a normal structure of spaces. (E.g.
in the above example, if the remote server is not in the space and does not know
whether the server is in the space or not it cannot return the room.)[↩](#a1)

@ -0,0 +1,286 @@
# MSC3030: Jump to date API endpoint
Add an API that makes it easy to find the closest messages for a given
timestamp.
The goal of this change is to have clients be able to implement a jump to date
feature in order to see messages back at a given point in time. Pick a date from
a calender, heatmap, or paginate next/previous between days and view all of the
messages that were sent on that date.
Alongside the [roadmap of feature parity with
Gitter](https://github.com/vector-im/roadmap/issues/26), we're also interested
in using this for a new better static Matrix archive. Our idea is to server-side
render [Hydrogen](https://github.com/vector-im/hydrogen-web) and this new
endpoint would allow us to jump back on the fly without having to paginate and
keep track of everything in order to display the selected date.
Also useful for archiving and backup use cases. This new endpoint can be used to
slice the messages by day and persist to file.
Related issue: [*URL for an arbitrary day of history and navigation for next and
previous days*
(vector-im/element-web#7677)](https://github.com/vector-im/element-web/issues/7677)
## Problem
These types of use cases are not supported by the current Matrix API because it
has no way to fetch or filter older messages besides a manual brute force
pagination from the most recent event in the room. Paginating is time-consuming
and expensive to process every event as you go (not practical for clients).
Imagine wanting to get a message from 3 years ago 😫
## Proposal
Add new client API endpoint `GET
/_matrix/client/v1/rooms/{roomId}/timestamp_to_event?ts=<timestamp>&dir=[f|b]`
which fetches the closest `event_id` to the given timestamp `ts` query parameter
in the direction specified by the `dir` query parameter. The direction `dir`
query parameter accepts `f` for forward-in-time from the timestamp and `b` for
backward-in-time from the timestamp. This endpoint also returns
`origin_server_ts` to make it easy to do a quick comparison to see if the
`event_id` fetched is too far out of range to be useful for your use case.
When an event can't be found in the given direction, the endpoint throws a 404
`"errcode":"M_NOT_FOUND",` (example error message `"error":"Unable to find event
from 1672531200000 in direction f"`).
In order to solve the problem where a homeserver does not have all of the history in a
room and no suitably close event, we also add a server API endpoint `GET
/_matrix/federation/v1/timestamp_to_event/{roomId}?ts=<timestamp>?dir=[f|b]` which other
homeservers can use to ask about their closest `event_id` to the timestamp. This
endpoint also returns `origin_server_ts` to make it easy to do a quick comparison to see
if the remote `event_id` fetched is closer than the local one. After the local
homeserver receives a response from the federation endpoint, it probably should
try to backfill this event via the federation `/event/<event_id>` endpoint so that it's
available to query with `/context` from a client in order to get a pagination token.
The heuristics for deciding when to ask another homeserver for a closer event if
your homeserver doesn't have something close, are left up to the homeserver
implementation, although the heuristics will probably be based on whether the
closest event is a forward/backward extremity indicating it's next to a gap of
events which are potentially closer.
A good heuristic for which servers to try first is to sort by servers that have
been in the room the longest because they're most likely to have anything we ask
about.
These endpoints are authenticated and should be rate-limited like similar client
and federation endpoints to prevent resource exhaustion abuse.
```
GET /_matrix/client/v1/rooms/<roomID>/timestamp_to_event?ts=<timestamp>&dir=<direction>
{
"event_id": ...
"origin_server_ts": ...
}
```
Federation API endpoint:
```
GET /_matrix/federation/v1/timestamp_to_event/<roomID>?ts=<timestamp>&dir=<direction>
{
"event_id": ...
"origin_server_ts": ...
}
```
---
In order to paginate `/messages`, we need a pagination token which we can get
using `GET /_matrix/client/r0/rooms/{roomId}/context/{eventId}?limit=0` for the
`event_id` returned by `/timestamp_to_event`.
We can always iterate on `/timestamp_to_event` later and return a pagination
token directly in another MSC ⏩
## Potential issues
### Receiving a rogue random delayed event ID
Since `origin_server_ts` is not enforcably accurate, we can only hope that an event's
`origin_server_ts` is relevant enough to its `prev_events` and descendants.
If you ask for "the message with `origin_server_ts` closest to Jan 1st 2018" you
might actually get a rogue random delayed one that was backfilled from a
federated server, but the human can figure that out by trying again with a
slight variation on the date or something.
Since there isn't a good or fool-proof way to combat this, it's probably best to just go
with `origin_server_ts` and not let perfect be the enemy of good.
### Receiving an unrenderable event ID
Another issue is that clients could land on an event they can't/won't render,
such as a reaction, then they'll be forced to desperately seek around the
timeline until they find an event they can do something with.
Eg:
- Client wants to jump to January 1st, 2022
- Server says there's an event on January 2nd, 2022 that is close enough
- Client finds out there's a ton of unrenderable events like memberships, poll responses, reactions, etc at that time
- Client starts paginating forwards, finally finding an event on January 27th it can render
- Client wasn't aware that the actual nearest neighbouring event was backwards on December 28th, 2021 because it didn't paginate in that direction
- User is confused that they are a month past the target date when the message is *right there*.
Clients can be smarter here though. Clients can see when events were sent as
they paginate and if they see they're going more than a couple days out, they
can also try the other direction before going further and further away.
Clients can also just explain to the user what happened with a little toast: "We
were unable to find an event to display on January 1st, 2022. The closest event
after that date is on January 27th."
### Abusing the `/timestamp_to_event` API to get the `m.room.create` event
Although it's possible to jump to the start of the room and get the first event in the
room (`m.room.create`) with `/timestamp_to_event?dir=f&ts=0`, clients should still use
`GET /_matrix/client/v3/rooms/{roomId}/state/m.room.create/` to get the room creation
event.
In the future, with things like importing history via
[MSC2716](https://github.com/matrix-org/matrix-spec-proposals/pull/2716), the first
event you encounter with `/timestamp_to_event?dir=f&ts=0` could be an imported event before
the room was created.
## Alternatives
We chose the current `/timestamp_to_event` route because it sounded like the
easist path forward to bring it to fruition and get some real-world experience.
And was on our mind during the [initial discussion](https://docs.google.com/document/d/1KCEmpnGr4J-I8EeaVQ8QJZKBDu53ViI7V62y5BzfXr0/edit#bookmark=id.qu9k9wje9pxm) because there was some prior art with a [WIP
implementation](https://github.com/matrix-org/synapse/pull/9445/commits/91b1b3606c9fb9eede0a6963bc42dfb70635449f)
from @erikjohnston. The alternatives haven't been thrown out for a particular
reason and we could still go down those routes depending on how people like the
current design.
### Paginate `/messages?around=<timestamp>` from timestamp
Add the `?around=<timestamp>` query parameter to the `GET
/_matrix/client/r0/rooms/{roomId}/messages` endpoint. This will start the
response at the message with `origin_server_ts` closest to the provided `around`
timestamp. The direction is determined by the existing `?dir` query parameter.
Use topological ordering, just as Element would use if you follow a permalink.
This alternative could be confusing to the end-user around how this plays with
the existing query parameters
`/messages?from={paginationToken}&to={paginationToken}` which also determine
what part of the timeline to query. Those parameters could be extended to accept
timestamps in addition to pagination tokens but then could get confusing again
when you start mixing timestamps and pagination tokens. The homeserver also has
to disambiguate what a pagination token looks like vs a unix timestamp. Since
pagination tokens don't follow a certain convention, some homeserver
implementations may already be using arbitrary number tokens already which would
be impossible to distinguish from a timestamp.
A related alternative is to use `/messages` with a `from_time`/`to_time` (or
`from_ts`/`to_ts`) query parameters that only accept timestamps which solves the
confusion and disambigution problem of trying to re-use the existing `from`/`to`
query paramters. Re-using `/messages` would reduce the number of round-trips and
potentially client-side implementations for the use case where you want to fetch
a window of messages from a given time. But has the same round-trip problem if
you want to use the returned `event_id` with `/context` or another endpoint
instead.
### Filter by date in `RoomEventFilter`
Extend `RoomEventFilter` to be able to specify a timestamp or a date range. The
`RoomEventFilter` can be passed via the `?filter` query param on the `/messages`
endpoint.
This suffers from the same confusion to the end-user of how it plays with how
this plays with `/messages?from={paginationToken}&to={paginationToken}` which
also determines what part of the timeline to query.
### Return the closest event in any direction
We considered omitting the `dir` parameter (or allowing `dir=c`) to have the server
return the closest event to the timestamp, regardless of direction. However, this seems
to offer little benefit.
Firstly, for some usecases (such as archive viewing, where we want to show all the
messages that happened on a particular day), an explicit direction is important, so this
would have to be optional behaviour.
For a regular messaging client, "directionless" search also offers little benefit: it is
easy for the client to repeat the request in the other direction if the returned event
is "too far away", and in any case it needs to manage an iterative search to handle
unrenderable events, as discussed above.
Implementing a directionless search on the server carries a performance overhead, since
it must search both forwards and backwards on every request. In short, there is little
reason to expect that a single `dir=c` request would be any more efficient than a pair of
requests with `dir=b` and `dir=f`.
### New `destination_server_ts` field
Add a new field and index on messages called `destination_server_ts` which
indicates when the message was received from federation. This gives a more
"real" time for how someone would actually consume those messages.
The contract of the API is "show me messages my server received at time T"
rather than the messy confusion of showing a delayed message which happened to
originally be sent at time T.
We've decided against this approach because the backfill from federated servers
could be horribly late.
---
Related issue around `/sync` vs `/messages`,
https://github.com/matrix-org/synapse/issues/7164
> Sync returns things in the order they arrive at the server; backfill returns
> them in the order determined by the event graph.
>
> *-- @richvdh, https://github.com/matrix-org/synapse/issues/7164#issuecomment-605877176*
> The general idea is that, if you're following a room in real-time (ie,
> `/sync`), you probably want to see the messages as they arrive at your server,
> rather than skipping any that arrived late; whereas if you're looking at a
> historical section of timeline (ie, `/messages`), you want to see the best
> representation of the state of the room as others were seeing it at the time.
>
> *-- @richvdh , https://github.com/matrix-org/synapse/issues/7164#issuecomment-605953296*
## Security considerations
We're only going to expose messages according to the existing message history
setting in the room (`m.room.history_visibility`). No extra data is exposed,
just a new way to sort through it all.
## Unstable prefix
While this MSC is not considered stable, the endpoints are available at `/unstable/org.matrix.msc3030` instead of their `/v1` description from above.
```
GET /_matrix/client/unstable/org.matrix.msc3030/rooms/<roomID>/timestamp_to_event?ts=<timestamp>&dir=<direction>
{
"event_id": ...
"origin_server_ts": ...
}
```
```
GET /_matrix/federation/unstable/org.matrix.msc3030/timestamp_to_event/<roomID>?ts=<timestamp>&dir=<direction>
{
"event_id": ...
"origin_server_ts": ...
}
```
Servers will indicate support for the new endpoint via a non-empty value for feature flag
`org.matrix.msc3030` in `unstable_features` in the response to `GET
/_matrix/client/versions`.

@ -0,0 +1,92 @@
# MSC3267: reference relationships
## Proposal
This proposal defines a relation type (using
[MSC2674 relations](https://github.com/matrix-org/matrix-doc/pull/2674))
for events to make a reference to another event.
A `rel_type` of `m.reference` is defined as a generic way to associate an
event with another event. As a bundle, `m.reference` relations appear as
an object with a single `chunk` field. The `chunk` is an array of objects
with a single `event_id` field for all the child events which `m.reference`
the parent.
There are no implied semantics by a reference relation: the feature or event
type which makes use of the `rel_type` should specify what sort of semantic
behaviour there is, if any. For example, describing that a poll response event
*references* the poll start event, or that a location update *references* a
previous location update.
Reference relations are used by [MSC2241](https://github.com/matrix-org/matrix-doc/pull/2241)
to tie all events together for the same verification request.
For instance, an `m.room.message` which references an existing event
would look like:
```json5
{
// Unimportant fields omitted
"type": "m.room.message",
"content": {
"msgtype": "m.text",
"body": "i <3 shelties",
"m.relates_to": {
"rel_type": "m.reference",
"event_id": "$another_event_id"
}
}
}
```
## Server aggregation
[MSC2674](https://github.com/matrix-org/matrix-doc/pull/2674) states
that values for `rel_type` should define how the server should aggregate the
`rel_type`. For `m.reference`, child events are bundled to appear as follows
under `m.relations`:
```json5
{
"m.reference": {
"chunk": [
{"event_id": "$one"},
{"event_id": "$two"},
{"event_id": "$three"}
]
}
}
```
Note that currently only the `event_id` is noted in the chunk, however a future
MSC might add more fields.
## Limitations
Different subtypes of references could be defined through additional fields on
the `m.relates_to` object, to distinguish between other forms of semantic behaviour
independent of type (hypothetical threads, replies, etc if we didn't have a system
for them already). This MSC doesn't attempt to define these subtypes.
This relation cannot be used in conjunction with another relation due to `rel_type`
being a single value. This is known and unfortunately not resolved by this MSC.
A future MSC might address the concern.
## Edge Cases
Can you reference an event which doesn't have logical meaning? Eg to a [reaction](https://github.com/matrix-org/matrix-doc/pull/2677)?
* Yes, at the protocol level. But you shouldn't expect clients to do anything
useful with it.
* The relationship is effectively pointless, so the event would appear as though
there was no reference relationship.
Do we need to support retrospective references?
* For something like "m.duplicate" to retrospectively declare that one event
dupes another, we might need to bundle two-levels deep (subject+ref and then
ref+target). We can cross this bridge when we get there though, as another
aggregation type
## Unstable prefix
Unfortunately not applicable - this MSC was used in production and appears in the
specified version of the [key verification framework](https://spec.matrix.org/v1.2/client-server-api/#key-verification-framework).

@ -0,0 +1,46 @@
# MSC3283: Expose enable_set_displayname, enable_set_avatar_url and enable_3pid_changes in capabilities response
Some home servers like [Synapse](https://github.com/matrix-org/synapse/blob/756fd513dfaebddd28bf783eafa95b4505ce8745/docs/sample_config.yaml#L1207)
can be configured to `enable_set_displayname: false`, `enable_set_avatar_url: false` or `enable_3pid_changes: false`.
To enable clients to handle that gracefully in the UI this setting should be exposed.
## Proposal
The `/_matrix/client/r0/capabilities` endpoint should be decorated to provide more information on capabilities.
```jsonc
{
"capabilities": {
"m.set_displayname": { "enabled": false },
"m.set_avatar_url": { "enabled": false },
"m.3pid_changes": { "enabled": false },
"m.room_versions": {...},
}
}
```
As part of this MSC, a capability for each setting will be added that exposes the server setting:
- `m.set_displayname`
Whether users are allowed to change their displayname after it has been initially set.
Useful when provisioning users based on the contents of a third-party directory.
- `m.set_avatar_url`
Whether users are allowed to change their avatar after it has been initially set.
Useful when provisioning users based on the contents of a third-party directory.
- `m.3pid_changes`
Whether users can change the 3PIDs associated with their accounts
(email address and msisdn).
Useful when provisioning users based on the contents of a third-party directory.
## Client recommendations
When presenting profile settings, clients should use capabilities in order to display the correct UI.
Capability should always be present.
Servers should always send these capabilities. If they aren't (because the server does not support
a new enough spec version or for any other reason), clients should behave as if they were present and set to true.
## Unstable prefix
While this MSC is not considered stable, implementations should use `org.matrix.msc3283.` in place of `m.` throughout this proposal.

@ -0,0 +1,51 @@
# Proposal to add timestamp massaging to the spec
Bridges often want to override message timestamps to preserve the timestamps from
the remote network. The spec used to have a concept of [timestamp massaging], but
it was excluded from the release due to not being properly specified. Synapse
still implements it and it is widely used in bridges.
[MSC2716] was originally going to add timestamp massaging to the spec, but it
pivoted to focusing solely on batch sending history. This MSC simply copies the
proposed `ts` query param from the [original MSC2716].
[timestamp massaging]: https://matrix.org/docs/spec/application_service/r0.1.2#timestamp-massaging
[MSC2716]: https://github.com/matrix-org/matrix-doc/pull/2716
[original MSC2716]: https://github.com/matrix-org/matrix-doc/blob/94514392b118dfae8ee6840b13b83d2f8ce8fcfc/proposals/2716-importing-history-into-existing-rooms.md
## Proposal
As per the original version of MSC2716:
> We let the AS API override ('massage') the `origin_server_ts` timestamp
> applied to sent events. We do this by adding a `ts` querystring parameter on
> the `PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}`
> and `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}`
> endpoints, specifying the value to apply to `origin_server_ts` on the event
> (UNIX epoch milliseconds).
>
> We consciously don't support the `ts` parameter on the various helper
> syntactic-sugar APIs like /kick and /ban. If a bridge/bot is smart enough to
> be faking history, it is already in the business of dealing with raw events,
> and should not be using the syntactic sugar APIs.
The spec should also make it clear that the `ts` query param won't affect DAG
ordering, and MSC2716's batch sending should be used when the intention is to
insert history somewhere else than the end of the room.
## Potential issues
None.
## Alternatives
The new MSC2716 could technically be considered an alternative, but it can only
be used for history, while this proposal also supports overriding timestamps of
new messages. In practice, bridges will likely use both: Batch sending for
filling history and timestamp massaging for new messages.
## Security considerations
Timestamps should already be considered untrusted over federation, and
application services are trusted server components, so allowing appservices
to override timestamps does not create any new security considerations.
## Unstable prefix
`org.matrix.msc3316.ts` may be used as the query parameter. However, the `ts`
parameter is already used in production for the `/send` endpoint, which means
the unstable prefix should only be used for the `/state` endpoint.

@ -98,7 +98,6 @@ for filename in os.listdir(cs_api_dir):
path = (basePath + path).replace('%CLIENT_MAJOR_VERSION%',
major_version)
for method, spec in methods.items():
if "tags" in spec.keys():
if path not in output["paths"]:
output["paths"][path] = {}
output["paths"][path][method] = spec

@ -22,7 +22,7 @@ python3 download_google_fonts_css.py \
Which would pop out a `Inter.css` file that should be `@import url("Inter.css")`d
somewhere in the site's SCSS (currently in
[/assets-hugo/scss/_variables_project.scss](/assets-hugo/scss/_variables_project.scss)).
[/assets/scss/_variables_project.scss](/assets/scss/_variables_project.scss)).
Re-running the script and committing any new files is only necessary when a desired
font updates (not very often), or we want to change the font we're using. In that case,

Loading…
Cancel
Save