Skip to content

feat(register): align with element-desktop — Phase 1+2 (MAS browser signup)#114

Open
TigerInYourDream wants to merge 12 commits intomainfrom
fix/register
Open

feat(register): align with element-desktop — Phase 1+2 (MAS browser signup)#114
TigerInYourDream wants to merge 12 commits intomainfrom
fix/register

Conversation

@TigerInYourDream
Copy link
Copy Markdown

Summary

  • Phase 1: dedicated src/register/ module with dual-mode capability discovery (MAS / UIAA / Disabled), homeserver URL input + validation, error states, back-to-login nav. Signup mode residue removed from LoginScreen.
  • Phase 2: MAS servers trigger system-browser launch to <issuer>/register via robius_open; no OAuth callback / token exchange (simple version — Element Desktop's full callback is future Phase 2.5).

Usable after merge

  • MAS homeservers (matrix.org, alvin.meldry.com, any MSC2965-compliant) fully supported end-to-end: click "Sign up here" → type server → sign up in system browser → return → log in with the new account via the existing password flow.
  • Non-MAS / UIAA-only / Disabled servers see an honest static status message; "Back to Login" never breaks.
  • All existing login paths unchanged.

Key design choices

  • MAS signup URL derived as <issuer>/register — NOT MSC2965's account field, which is for post-login account management and redirect-loops when opened unauthenticated (verified on alvin.meldry.com).
  • MAS detection accepts both stable m.authentication and unstable org.matrix.msc2965.authentication — alvin uses stable, matrix.org still uses unstable; missing either key would mis-classify the server as Disabled.
  • Fire-and-forget browser launch via robius_open::Uri::new(url).open() (no callback / custom URI scheme) — user completes signup on web, returns to robrix2, logs in manually.

Test plan

  • alvin.meldry.com (MAS, open registration): browser opens, "Create account" form loads, can complete signup
  • matrix.org (MAS, restricted registration): browser opens to MAS, page handled cleanly
  • Empty URL input → "Please enter a homeserver URL..."
  • Bad scheme ftp://…"Unsupported scheme: ftp. Only http(s) is allowed."
  • Unreachable server → "Could not reach that server: …"
  • Theme + layout aligns with LoginScreen (light bg, COLOR_TEXT, TITLE_TEXT / REGULAR_TEXT)
  • Existing login regression: password login + SSO unaffected

References

  • Spec: specs/task-register-flow.spec.md
  • Plans: docs/superpowers/plans/2026-04-21-register-phase-1-skeleton.md, docs/superpowers/plans/2026-04-21-register-phase-2-mas-browser.md
  • Future scope (PR#2): Phase 3 UIAA native form, Phase 4 SSO provider buttons, Phase 5 password strength (zxcvbn)

Add empty register module with mod.rs, register_screen.rs,
register_status_modal.rs, validation.rs. Register the module
in src/lib.rs and wire the script_mod aggregator per the
login/mod.rs pattern.

Part of specs/task-register-flow.spec.md Phase 1.
Accept bare hostname (prepend https://), strip trailing slash,
reject non-http(s) schemes and empty input. 8 unit tests cover
the edge cases.
Introduce the data model used across Phase 1-5:
- HsCapabilities with is_mas_native_oidc / registration_enabled /
  uiaa_probe / sso_providers fields matching the spec
- RegisterMode enum (MasWebOnly / Uiaa / Disabled) derived via
  HsCapabilities::mode() — MAS wins over UIAA per element-web rule
- RegisterAction with NavigateToLogin / CapabilitiesDiscovered /
  DiscoveryFailed variants for Phase 1 + None default
Add LoginAction::NavigateToRegister variant. Replace
set_signup_mode(true) call with Cx::post_action dispatch so
the main App can route to the new RegisterScreen.

The signup-mode rendering code is removed in a later task.
Implement MatrixRequest::DiscoverHomeserverCapabilities handler
that probes .well-known, /versions, /v3/login, and empty POST
/register to build HsCapabilities. Results post back as
RegisterAction::CapabilitiesDiscovered / DiscoveryFailed.

RegisterScreen widget renders the homeserver input, Next button,
and three-state status area (MAS / UIAA / Disabled / errors).
The full wizard body is added in Phase 2+.
Delete confirm_password input, mode-toggle state (signup_mode field,
set_signup_mode, sync_mode_texts), and the signup submit branch in
handle_actions. The "Sign up here" button (DSL id mode_toggle_button)
keeps its text, position and style; its click now posts
LoginAction::NavigateToRegister (wired in f40871d) — the navigation
handler lands in a follow-up commit (Task 8).

Registration logic moves wholesale to src/register/; see
specs/task-register-flow.spec.md.
Handle LoginAction::NavigateToRegister and
RegisterAction::NavigateToLogin to toggle visibility between
login_screen_view and the new register_screen_view. Both screens
live as siblings under the overlay_container; default visibility
stays on LoginScreen.

auth_ui_state and update_login_visibility are intentionally left
untouched — Phase 2+ will layer auth-state machinery on top of
this minimal inline toggle.
Task 9 testing found two bugs:

1. register_screen.rs hard-coded dark-theme colors (#x1F2124 bg,
   #xF1F2F3 text) conflicted with the project's light palette;
   combined with `draw_bg:` (replace) instead of `draw_bg +:`
   (merge, Pitfall #44) the shader silently fell back to
   transparent and text appeared washed out. Replace all hex
   colors with the shared COLOR_SECONDARY / COLOR_TEXT tokens
   and use TITLE_TEXT / REGULAR_TEXT styles, matching
   login_screen conventions.

2. MAS detection only looked at the stable .well-known key
   `m.authentication.issuer`. matrix.org still serves the
   unstable key `org.matrix.msc2965.authentication.issuer`,
   so it was mis-classified as non-MAS and fell through to the
   "registration disabled" branch. Accept both keys (element-web
   does the same).
Runtime DSL errors surfaced at `cargo run`:

1. register_status_modal.rs used Makepad 1.x live_design! form
   `pub RegisterStatusModal := {{RegisterStatusModal}} View {...}`.
   In script_mod! the 2.0 form is
   `mod.widgets.RegisterStatusModal = #(RegisterStatusModal::register_widget(vm)) {...}`
   (Pitfall #43). The 1.x form emitted
   "variable pub not found in scope" +
   "variable RegisterStatusModal not found in scope".

2. register_screen.rs set `empty_message:` on a RobrixTextInput.
   The correct property is `empty_text:` (the error helper
   suggested it exactly). Symptom: the homeserver input was
   never created because its property bag failed to bind.

Both failures pass `cargo build` because script_mod! bodies
are parsed at widget init by the Makepad script VM, not by
rustc. Caught when running the app.
Phase 2 groundwork. HsCapabilities grows a new Option<String>
field populated from the same .well-known probe Phase 1 already
runs, preferring the explicit `account` field and falling back
to `<issuer>/account/` when absent (alvin.meldry.com currently
omits the field; matrix.org provides it). No extra HTTP round
trips. The URL is consumed in the next commit by RegisterScreen
to launch the system browser.
When CapabilitiesDiscovered reports MasWebOnly, call
robius_open::Uri::new(&url).open() on the mas_account_url
captured in the previous commit, then replace the status text
with a user-facing instruction: complete signup in the browser,
return, hit Back to Login, sign in with the new credentials.

Handles three paths:
- browser opens cleanly -> instruction text
- robius_open errors    -> fallback text with the URL for copy
- mas_account_url None  -> defensive message ("advertises but
                           no signup URL was found")

Simple-version scope: no OAuth callback, no token exchange, no
Dynamic Client Registration. Element Desktop's full callback
flow is Phase 2.5 if we decide to ship it. With this commit the
alvin.meldry.com / matrix.org paths are end-to-end usable: user
completes signup in the browser and returns to log in via the
existing password flow.
The previous commit opened <issuer>/account/, which is MSC2965's
account-management URI and requires an authenticated session —
hitting it without a cookie loops between /account/ and /login
(verified in incognito against alvin.meldry.com). MAS exposes the
direct self-registration form at <issuer>/register; confirmed to
render a proper "Create account" page with username/email/phone/
password fields on alvin.meldry.com.

Rename the field from mas_account_url to mas_signup_url to match
the real semantics, and drop the `account` field lookup from
discovery — we do not use it and keeping it as a fallback only
invited this bug. Future account-management features in later
phases can add it back when there is a real consumer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant