Settings: tabs instead of one long page, and a review pass (#5916)

Settings was every registry group, then invites, accounts, groups and
activity, in one scroll. It is now six tabs held in ?tab=:
- General: General, Notes, Attachments, Links.
- People: Access, Invites, Accounts, Groups.
- Email, with a line saying whether email is on and what it still needs.
- Security.
- Activity.
A registry group that no tab names lands on General, so it can't go missing.

Save saves only the current tab. Edits waiting on other tabs keep their
values and mark their tab with a dot. Leaving the page with unsaved edits
asks first.

Review fixes:
- On a phone, a field stacks under its label instead of overflowing beside
  it.
- Descriptions are easier to read.
- The SMTP encryption choice shows readable names (choice_labels).
- The test-email button uses .btn-sm and is disabled while email is off.
- The accounts list is headed Accounts, not People, now that People is a
  tab.
- The docs point at the new paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 13:41:03 -04:00
co-authored by Claude Opus 5.5
parent c63375b092
commit 5cc71c3050
7 changed files with 241 additions and 83 deletions
+8
View File
@@ -68,3 +68,11 @@ def test_the_public_address_is_an_http_url_without_a_trailing_slash():
assert validate_updates({"public_url": ""}) == ({"public_url": ""}, None)
clean, error = validate_updates({"public_url": "javascript:alert(1)"})
assert clean == {} and error
def test_choice_labels_name_every_choice():
"""The UI pairs labels with choices by position, so a label list of another length
would put one choice's name on another."""
for d in REGISTRY:
if d.choice_labels is not None:
assert d.choices is not None and len(d.choice_labels) == len(d.choices), d.key