feat(web): service-worker notifications and composer/scroll UX fixes
Some checks failed
CI / test (push) Failing after 21s

- register notifications service worker and handle click-to-open chat/message

- route realtime notifications through service worker with fallback

- support ?chat=&message= deep-link navigation in chats page

- enforce 1s minimum voice message length

- lift scroll-to-bottom button to avoid overlap with composer action
This commit is contained in:
2026-03-08 11:33:58 +03:00
parent 68ba97bb90
commit 4fe89ce89a
7 changed files with 179 additions and 53 deletions

View File

@@ -280,15 +280,21 @@ export function MessageComposer() {
recorder.ondataavailable = (event) => chunksRef.current.push(event.data);
recorder.onstop = async () => {
const shouldSend = sendVoiceOnStopRef.current;
const durationMs = recordingStartedAtRef.current ? Date.now() - recordingStartedAtRef.current : 0;
const data = [...chunksRef.current];
chunksRef.current = [];
if (recordingStreamRef.current) {
recordingStreamRef.current.getTracks().forEach((track) => track.stop());
recordingStreamRef.current = null;
}
recordingStartedAtRef.current = null;
if (!shouldSend || data.length === 0) {
return;
}
if (durationMs < 1000) {
setUploadError("Voice message is too short. Minimum length is 1 second.");
return;
}
const blob = new Blob(data, { type: "audio/webm" });
const file = new File([blob], `voice-${Date.now()}.webm`, { type: "audio/webm" });
await handleUpload(file, "voice");
@@ -313,7 +319,6 @@ export function MessageComposer() {
recorderRef.current.stop();
}
recorderRef.current = null;
recordingStartedAtRef.current = null;
setRecordingState("idle");
setRecordSeconds(0);
}
@@ -567,8 +572,9 @@ export function MessageComposer() {
disabled={recordingState !== "idle" || !activeChatId}
onClick={handleSend}
type="button"
title="Send message"
>
</button>
) : (
<button
@@ -578,6 +584,7 @@ export function MessageComposer() {
disabled={isUploading || !activeChatId}
onPointerDown={onMicPointerDown}
type="button"
title="Hold to record voice"
>
🎤
</button>

View File

@@ -494,11 +494,12 @@ export function MessageList() {
<div className="px-5 pb-2 text-xs text-slate-300/80">{(typingByChat[activeChatId] ?? []).length > 0 ? "typing..." : ""}</div>
{showScrollToBottom ? (
<div className="pointer-events-none absolute bottom-4 right-4 z-40">
<div className="pointer-events-none absolute bottom-20 right-4 z-40 md:bottom-24">
<button
className="pointer-events-auto inline-flex h-10 w-10 items-center justify-center rounded-full border border-slate-600/80 bg-slate-900/90 text-lg text-slate-100 shadow-lg hover:bg-slate-800"
onClick={scrollToBottom}
type="button"
title="Scroll to latest"
>
</button>