feat(messages): счётчики упоминаний, квота приглашений и read state автора

- упоминания в сообщении увеличивают счётчик упоминаний комнаты у упомянутых
  (store.BumpMentionCount) и отправляют им READ_STATE_UPDATE на все устройства;
- отправка сообщения отмечает его прочитанным для автора;
- подтверждение прочтения сбрасывает упоминания;
- суточная квота 10 приглашений на пользователя (AGENT.md 8.6), администратор
  инстанса её обходит; httpx.NewRateLimiterWindow для произвольных окон;
- тест на рост и сброс счётчика упоминаний.
This commit is contained in:
2026-09-19 23:37:02 +03:00
parent 0e39b8e027
commit 8619f37cd5
15 changed files with 186 additions and 6 deletions
+2 -1
View File
@@ -93,8 +93,9 @@ export function Menu({ open, label, onClose, children, anchorRef, className = ''
const onKeyDown = (event: ReactKeyboardEvent<HTMLDivElement>): void => {
if (event.key === 'Tab') {
// Меню — не часть tab-порядка: уводим фокус дальше по странице.
// Меню — не часть tab-порядка: закрываем его и оставляем фокус на якоре.
onClose();
anchorRef?.current?.focus();
return;
}
const items = Array.from(panelRef.current?.querySelectorAll<HTMLElement>(ITEM_SELECTOR) ?? []);
+1 -1
View File
@@ -52,7 +52,7 @@ export default function LoginPage() {
const registrationEnabled = instance.data?.registration_enabled === true;
return (
<main className="mx-auto flex min-h-full w-full max-w-md flex-col justify-center gap-5 px-4 py-10">
<main className="gl-page mx-auto flex min-h-full w-full max-w-md flex-col justify-center gap-5 px-4 py-10">
<header>
<h1 className="text-2xl font-semibold tracking-tight">{t('auth.login.title')}</h1>
<p className="mt-1 text-fg-muted">{t('auth.login.subtitle')}</p>
+1 -1
View File
@@ -131,7 +131,7 @@ export default function OnboardingPage() {
}
return (
<main className="mx-auto flex min-h-full w-full max-w-xl flex-col justify-center gap-5 px-4 py-10">
<main className="gl-page mx-auto flex min-h-full w-full max-w-xl flex-col justify-center gap-5 px-4 py-10">
<header>
<h1 className="text-2xl font-semibold tracking-tight">
{suggestedName === ''
+1 -1
View File
@@ -111,7 +111,7 @@ export default function RegisterPage() {
}
return (
<main className="mx-auto flex min-h-full w-full max-w-md flex-col justify-center gap-5 px-4 py-10">
<main className="gl-page mx-auto flex min-h-full w-full max-w-md flex-col justify-center gap-5 px-4 py-10">
<header>
<h1 className="text-2xl font-semibold tracking-tight">{t('auth.register.title')}</h1>
<p className="mt-1 text-fg-muted">
+1 -1
View File
@@ -40,7 +40,7 @@ export default function StatusPage() {
};
return (
<div className="mx-auto flex min-h-full w-full max-w-3xl flex-col gap-6 px-4 py-10">
<div className="gl-page mx-auto flex min-h-full w-full max-w-3xl flex-col gap-6 px-4 py-10">
<header className="flex flex-wrap items-center justify-between gap-4">
<div>
<h1 className="text-2xl font-semibold tracking-tight">{t('app.name')}</h1>