feat: extended profile fields, push rule editor, server ACL editor

P2-8: Pronouns (m.pronouns) and Timezone (m.tz) fields in Settings →
Account → Profile; saved via MSC4133 PUT /profile/{userId}/{field};
useExtendedProfile hook fetches both in parallel; UserHero displays
pronouns below display name and timezone string below username

P2-11: Full push rule editor in Settings → Notifications below keyword
rules; covers override/room/sender/underride rule kinds; enable/disable
toggle per rule, human-readable labels for built-in rules, delete button
for custom rules, add-rule form for room and sender rules

P2-12: Server ACL viewer/editor in room settings (Server ACL tab);
reads m.room.server_acl state event; allow/deny server lists with
wildcard validation; allow IP literals toggle; power-level gated
(edit requires sufficient PL, otherwise read-only view)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 23:13:33 -04:00
parent 504c092c17
commit 877e1eaca7
9 changed files with 1157 additions and 43 deletions
@@ -31,6 +31,7 @@ import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare';
import { CreatorChip } from './CreatorChip';
import { getDirectCreatePath, withSearchParam } from '../../pages/pathUtils';
import { DirectCreateSearchParams } from '../../pages/paths';
import { useExtendedProfile } from '../../hooks/useExtendedProfile';
type VerifyDeviceButtonProps = {
userId: string;
@@ -243,6 +244,7 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
const avatarUrl = (avatarMxc && mxcUrlToHttp(mx, avatarMxc, useAuthentication)) ?? undefined;
const presence = useUserPresence(userId);
const extProfile = useExtendedProfile(userId);
const handleMessage = () => {
closeUserRoomProfile();
@@ -262,7 +264,13 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
<Box direction="Column" gap="500" style={{ padding: config.space.S400 }}>
<Box direction="Column" gap="400">
<Box gap="400" alignItems="Center">
<UserHeroName displayName={displayName} userId={userId} status={presence?.status} />
<UserHeroName
displayName={displayName}
userId={userId}
status={presence?.status}
pronouns={extProfile.pronouns}
timezone={extProfile.timezone}
/>
{showEncryption && <MemberVerificationBadge userId={userId} />}
{userId !== myUserId && (
<Box shrink="No">