feat: presence fix, voice ringing fix, user private notes + doc updates
CI / Build & Quality Checks (push) Successful in 10m22s
Trigger Desktop Build / trigger (push) Successful in 5s

- usePresenceUpdater: replace stale closure with readStatus() called at
  invocation time so changing custom status in Profile Settings is never
  silently overwritten by subsequent activity events
- CallEmbedProvider: fix m.space.parent state-key lookup by switching
  getStateEvent → getStateEvents (plural); space channel voice rooms no
  longer trigger the incoming-call ring/animation
- Add useUserNotes hook (io.lotus.user_notes account data, reactive via
  useAccountDataCallback, 500-char limit, cross-device sync)
- UserRoomProfile: add UserPrivateNotes textarea with 800ms debounced
  auto-save, saving indicator, char counter when <100 chars remain;
  shown only when viewing another user's profile
- LOTUS_FEATURES.md: add Private Notes section, Status Revert fix note,
  animation improvements subsection, Seasonal Themes section
- LOTUS_BUGS.md: mark presence revert + voice ringing bugs as resolved
- README.md + landing/index.html: document all new June 2026 features

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 00:47:14 -04:00
co-authored by Claude Sonnet 4.6
parent 6db07f1371
commit ca09e8e6ca
7 changed files with 228 additions and 36 deletions
+56
View File
@@ -0,0 +1,56 @@
import { useCallback, useEffect, useState } from 'react';
import { MatrixClient } from 'matrix-js-sdk';
import { useMatrixClient } from './useMatrixClient';
import { useAccountDataCallback } from './useAccountDataCallback';
const NOTES_KEY = 'io.lotus.user_notes';
export const USER_NOTE_MAX_LENGTH = 500;
type UserNotesContent = Record<string, string>;
function readNotes(mx: MatrixClient): UserNotesContent {
return (mx.getAccountData(NOTES_KEY as any)?.getContent() as UserNotesContent | undefined) ?? {};
}
export function useUserNotes(): {
getNote: (userId: string) => string;
setNote: (userId: string, note: string) => Promise<void>;
} {
const mx = useMatrixClient();
const [notes, setNotes] = useState<UserNotesContent>(() => readNotes(mx));
useAccountDataCallback(
mx,
useCallback(
(evt) => {
if (evt.getType() === NOTES_KEY) {
setNotes(evt.getContent<UserNotesContent>() ?? {});
}
},
[],
),
);
useEffect(() => {
setNotes(readNotes(mx));
}, [mx]);
const getNote = useCallback((userId: string) => notes[userId] ?? '', [notes]);
const setNote = useCallback(
async (userId: string, note: string) => {
const current = readNotes(mx);
const updated = { ...current };
const trimmed = note.trim().slice(0, USER_NOTE_MAX_LENGTH);
if (trimmed) {
updated[userId] = trimmed;
} else {
delete updated[userId];
}
await (mx as any).setAccountData(NOTES_KEY, updated);
},
[mx],
);
return { getNote, setNote };
}