Skip to content

feat(space): round out Matrix Spaces support - #292

Merged
tyreseluo merged 3 commits into
mainfrom
feat/matrix-spaces
Aug 7, 2026
Merged

feat(space): round out Matrix Spaces support#292
tyreseluo merged 3 commits into
mainfrom
feat/matrix-spaces

Conversation

@tyreseluo

Copy link
Copy Markdown

Spaces could previously only be browsed. This adds the write side — joining, creating, curating, configuring and leaving them — plus the read-side pieces that were missing.

Commits

refactor(ui) migrate the space lobby to RBX design tokens
refactor(ui) rebuild the room settings dialog layout
feat(space) round out Matrix Spaces support

The two visual refactors are separate commits per ui-visual-spec-zh.md §0.1, and each commit in the series builds on its own.

What's new

Invites. Invited spaces now pass the room list filter, so a space invite reaches the invites section instead of being dropped; joined spaces stay filtered out, since the SpacesBar owns those. Accepting hands the space to its lobby (dock tab on desktop, space tab on mobile).

Creating. CreateRoom can emit m.space via creation_content. Entry points are "New space" in the add menu and "New subspace" in a lobby. A new top-level space is selected once the space service reports it as joined — it cannot be selected before that.

Curating children. AddRoomToSpace / RemoveRoomFromSpace, a modal to link an existing room in, and a per-child remove action behind a confirmation.

Settings. The room settings dialog now serves spaces too — a space is a room underneath, so name/topic/avatar/addresses are identical; only the wording differs and the timeline-media section is hidden. Adds a join-rule control and a Members tab.

Leaving. A Leave action in the lobby header, available to every member.

Read side. Unread badges on the SpacesBar aggregate a space's rooms; desktop lands on the lobby when a space is selected.

Notes for review

A few decisions worth a second opinion:

  • remove_room() now reads the live room state instead of the cached one. A room that stops matching the list filter is removed carrying a stale state, and that distinction is exactly what tells us an accepted space invite has become a joined space. This touches the normal room-removal path too.
  • Restricted join rules are shown read-only. Rewriting one from a three-way choice would discard its allow-list and lock people out, so the dialog displays the rule and explains why it can't be edited here.
  • Children are no longer re-sorted alphabetically. The SDK already orders them per the spec (m.space.child order, then timestamp, then room ID); the local sort was silently discarding the ordering a space's admins had set. Visible consequence: subspaces no longer float above rooms, since the spec orders them together. Happy to reinstate the grouping as a secondary sort if that reads better.
  • GetCreatableSpaces now reports two lists, because "can add children" (m.space.child) and "can edit settings" (m.room.name) are different power levels and the settings gear must follow the latter.
  • Leaving is not gated on the edit permission. It used to live only in the settings dialog, which is gated, so a plain member who joined a space had no way back out.

Testing

cargo build and cargo test --lib (596 pass) on every commit; smoke-run shows no DSL runtime errors.

Manual testing is incomplete — the join-rule control, the Members tab and the leave action have not been exercised against a live homeserver yet. The riskiest path to verify is opening settings on a restricted room and confirming it renders the read-only note rather than the three-way choice.

🤖 Generated with Claude Code

tyreseluo and others added 3 commits August 7, 2026 13:05
The space lobby carried its own parallel set of visual constants, which
is why it drifted from the rest of the app: bare hex literals, legacy
COLOR_* tokens and ad-hoc font sizes, all of which docs/ui-visual-spec-zh.md
§0.1 forbids.

- 10 bare hex literals and the legacy COLOR_* tokens now resolve through
  RBX_* instead.
- Ad-hoc font sizes replaced by the RBX_TEXT_* presets.
- The "Suggested" tag follows the badge contract in §4.2: pill radius,
  soft-accent fill, accent text, no border.
- DrawTreeLine gained a `line_color` uniform, so the connector shader no
  longer hard-codes grey and takes RBX_DIVIDER from the DSL.
- Header and screen surfaces unified, with a divider hairline under the
  header now that they share a colour.
- Secondary header actions demoted to neutral buttons, leaving one
  primary action instead of four competing filled ones.

No behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dialog sized itself with `height: Fit` while pinning its ScrollYView
to 520px inside it. A scroll view needs a resolved viewport, so instead
of scrolling it simply stretched: the form spilled out at full length,
leaving Save/Cancel stranded in the middle of it and the Advanced section
below them.

- Root is a fixed height, giving the ScrollYView a real viewport, and
  restructured as header / scrolling body / sticky footer.
- Save and Cancel moved into that footer. They act on the whole dialog,
  so they should not scroll away.
- The sidebar was `height: Fit`, so its surface collapsed to a single row
  next to a much taller content area — the "General" entry appeared to
  float. It now fills the body, with a hairline against the content, and
  the selected row reads as selected: teal rail, tinted background, bold
  accent label.
- Content became a sunken canvas holding white section cards (§4.1),
  replacing one long flat form divided by hairlines.
- Remaining ad-hoc font sizes and legacy colour tokens moved to the
  RBX_TEXT_* presets and RBX palette; radio buttons follow the brand
  accent rather than the legacy blue.
- The modal centred with `align: y: 0.1`, i.e. deliberately near the top.
  It is 0.5 now; the fixed height above is what lets that take effect,
  since a `Fill` child would claim all the space and leave nothing to
  centre with.

DSL only; the dialog's behaviour is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Spaces could previously only be browsed. This adds the write side —
joining, creating, curating, configuring and leaving them — plus the
pieces of the read side that were missing.

Invites
- Let invited spaces through the room list filter, so a space invite
  reaches the invites section instead of being dropped. Joined spaces
  stay filtered out; the SpacesBar still owns those.
- `remove_room()` now reads the live room state rather than the cached
  one, because a room that stops matching the filter is removed carrying
  a stale state. That distinction is what tells us an invite was accepted.
- Accepting hands the space to its lobby (dock tab on desktop, space tab
  on mobile); declining just drops the entry.

Creating
- `CreateRoom` can emit `m.space` via `creation_content`; spaces skip the
  encryption initial state since they carry no messages.
- Entry points: "New space" in the add menu, "New subspace" in a lobby.
- A newly created top-level space is selected once the space service
  reports it as joined — it cannot be selected before that.

Curating children
- `AddRoomToSpace` / `RemoveRoomFromSpace`. Removal overwrites
  `m.space.child` with empty content, since state events cannot be
  deleted and a link without `via` is not a valid one.
- New AddExistingRoomModal picks from already-joined rooms; the lobby
  grows a per-child "remove from space" action behind a confirmation.
- Child rows resolve permission against their *direct* parent, which may
  be a nested subspace rather than the lobby's own space.

Settings
- The room settings dialog now serves spaces too: a space is a room
  underneath, so name/topic/avatar/addresses are identical. Only the
  wording differs, and the timeline-media section is hidden.
- Join rules are readable and settable (invite / knock / public).
  Restricted rules are shown read-only: rewriting one would discard its
  allow-list and lock people out.
- A Members tab lists joined members, sorted by power level.
- `GetCreatableSpaces` reports "can add children" (m.space.child) and
  "can edit settings" (m.room.name) separately — different power levels,
  and the settings gear must follow the latter.

Leaving
- The lobby header gets a Leave action, available to every member rather
  than only administrators. Leaving used to live solely in the settings
  dialog, which is gated on the edit permission, so a plain member who
  joined a space had no way back out.

Read side
- Unread badges on the SpacesBar aggregate every joined room in a space,
  guarding against `m.space.child` cycles and double-counting.
- Stop re-sorting a space's children alphabetically. The SDK already
  orders them per the spec (m.space.child `order`, then timestamp, then
  room ID); the local sort was silently discarding the ordering that a
  space's admins had set.
- Selecting a space on desktop lands on its lobby instead of the generic
  Welcome tab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tyreseluo
tyreseluo merged commit ceaa0f6 into main Aug 7, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant