This commit is contained in:
Arjun Patel
2026-06-01 17:08:25 -07:00
parent 78456a4b1d
commit d9c2a1ed1b
8 changed files with 22 additions and 21 deletions
+1
View File
@@ -15,6 +15,7 @@
"invalidate-gcs-cache": "gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/arm64/RELEASES.json && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/darwin/x64/RELEASES.json && gsutil setmeta -h 'Cache-Control:no-cache, no-store, must-revalidate' gs://flowy-releases/llink/win32/x64/RELEASES",
"lint": "eslint --ext .ts,.tsx .",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
"compile": "npx tsc --noEmit 2>&1 | grep '^src/'",
"web:dev": "cross-env APP_ENV=dev vite --config vite.web.config.mts",
"web:build": "cross-env APP_ENV=prod vite build --config vite.web.config.mts",
@@ -136,7 +136,6 @@ export function ComposeOverlay({
const cancel = useCallback(() => {
setStepSync('idle');
setError(null);
setTextContent('');
setMediaStream(null);
setReviewBlob(null);
setReviewDurationMs(0);
@@ -150,8 +149,7 @@ export function ComposeOverlay({
const addAttachments = useCallback(
async (files: File[]) => {
const currentCount = attachments.length;
const available = MAX_ATTACHMENTS - currentCount;
const available = MAX_ATTACHMENTS - attachments.length;
if (available <= 0) {
toast.error(`Maximum ${MAX_ATTACHMENTS} attachments`);
return;
@@ -404,6 +402,7 @@ export function ComposeOverlay({
try {
await createChildParticle(targetPath);
cancel();
setTextContent('');
} catch (err) {
if (!handleQuotaError(err)) throw err;
}
@@ -413,7 +412,7 @@ export function ComposeOverlay({
setStepSync,
createChildParticle,
cancel,
handleQuotaError,
handleQuotaError
]);
// New stream mode: create stream + first child
@@ -434,8 +433,8 @@ export function ComposeOverlay({
const streamChildrenPath = particlePath(networkId, [streamId]);
await createChildParticle(streamChildrenPath);
cancel();
setTextContent('');
} catch (err) {
if (!handleQuotaError(err)) throw err;
}
@@ -23,10 +23,13 @@ export function TextReactionInput({
useSuspendPlayback(open, 'text-reaction');
// Clear the input each time the popup opens.
if (open !== prevOpen) {
setPrevOpen(open);
if (open) setValue('');
// NOTE: perform side effects here when opening
if (open) {
setValue('')
}
}
useEffect(() => {
@@ -44,6 +47,7 @@ export function TextReactionInput({
const handleSubmit = () => {
if (!canSubmit) return;
onSubmit(trimmed);
setValue('');
onClose();
};
@@ -36,8 +36,7 @@ function usePreviewStream(
const [error, setError] = useState<string | null>(null);
const [prevEnabled, setPrevEnabled] = useState(enabled);
// Clear the preview when disabled; the effect below only manages the
// getUserMedia subscription.
// Clear the preview when disabled
if (enabled !== prevEnabled) {
setPrevEnabled(enabled);
if (!enabled) {
-1
View File
@@ -62,7 +62,6 @@ export function useChannel(channelId: string | null): UseChannelResult {
client.off(channelId, 'leave', onLeave);
client.off(channelId, 'message', onMessage);
client.unsubscribe(channelId);
// Clear on teardown so a new channel doesn't briefly show stale data.
setPresence([]);
setMessages([]);
};
+9 -2
View File
@@ -75,8 +75,15 @@ export function useMediaDevices(): UseMediaDevicesResult {
}, [refresh]);
return {
audioInputs: devices.filter((d) => d.kind === 'audioinput'),
videoInputs: devices.filter((d) => d.kind === 'videoinput'),
// Before permission is granted, enumerateDevices returns placeholder
// entries with an empty deviceId — filter them out so consumers never
// render an empty-value <SelectItem />, which Radix rejects.
audioInputs: devices.filter(
(d) => d.kind === 'audioinput' && d.deviceId !== '',
),
videoInputs: devices.filter(
(d) => d.kind === 'videoinput' && d.deviceId !== '',
),
permissionState,
refresh,
requestLabels,
+1 -6
View File
@@ -3,11 +3,6 @@ import { useEffect, useState } from 'react';
/**
* Creates an object URL for a Blob/File and revokes it when the source changes
* or the component unmounts. Returns null when given null.
*
* `createObjectURL` is an imperative side effect that must run inside an effect,
* so publishing the resulting URL to state here is genuine external-resource
* synchronization rather than a render cascade — hence the single, contained
* lint suppression below.
*/
export function useObjectUrl(source: Blob | null): string | null {
const [url, setUrl] = useState<string | null>(null);
@@ -15,7 +10,7 @@ export function useObjectUrl(source: Blob | null): string | null {
useEffect(() => {
if (!source) return;
const objectUrl = URL.createObjectURL(source);
// Intended external-resource publish (see hook doc), not a render cascade.
// Intended external-resource publish to sync, not a render cascade.
// eslint-disable-next-line react-hooks/set-state-in-effect
setUrl(objectUrl);
return () => {
-3
View File
@@ -42,7 +42,6 @@ export function useLiveParticle(path: ParticlePath): UseLiveParticleResult {
return () => {
unsubscribe();
// Reset on teardown so a new path doesn't flash the previous particle.
setIsLoading(true);
setError(null);
setParticle(null);
@@ -121,7 +120,6 @@ export function useLiveParticleChildren(
return () => {
unsubscribe();
// Reset on teardown so a new path doesn't flash the previous children.
setChildren([]);
setError(null);
setIsLoading(true);
@@ -168,7 +166,6 @@ export function useLiveLatestChild(
return () => {
unsubscribe();
// Reset on teardown so a new path doesn't flash the previous child.
setIsLoading(true);
setLatestChild(null);
};