55 collection ,
66 doc ,
77 getDoc ,
8+ getDocs ,
89 onSnapshot ,
910 orderBy ,
1011 query ,
@@ -31,10 +32,12 @@ import { getAppCopy } from './i18n/copy';
3132import firebaseConfig from '../firebase-applet-config.json' ;
3233import {
3334 buildUnknownQueueTargetFingerprint ,
35+ classifyUnknownQueueLoadFailure ,
3436 classifyHouseholdMembershipProbe ,
35- getUnknownQueueLoadErrorMessage ,
3637 HouseholdMembershipProbeResult ,
3738 isFirestoreFailedPreconditionError ,
39+ isFirestorePermissionDeniedError ,
40+ type UnknownQueueReadProbeResult ,
3841 sortUnknownIngredientQueueItemsByCreatedAt ,
3942 toFirestoreListenerErrorInfo ,
4043} from './utils/unknownQueue' ;
@@ -80,6 +83,7 @@ export default function App() {
8083 const [ inviteEmail , setInviteEmail ] = useState ( '' ) ;
8184 const [ isInviting , setIsInviting ] = useState ( false ) ;
8285 const [ uiFeedback , setUiFeedback ] = useState < UiFeedback | null > ( null ) ;
86+ const [ unknownQueueWarning , setUnknownQueueWarning ] = useState < string | null > ( null ) ;
8387 const isOwner = role === 'owner' ;
8488 const ownerLanguage : UiLanguage = householdData ?. ownerLanguage ?? 'en' ;
8589 const cookLanguage : UiLanguage = householdData ?. cookLanguage ?? 'hi' ;
@@ -99,6 +103,7 @@ export default function App() {
99103 setHouseholdId ( null ) ;
100104 setHouseholdData ( null ) ;
101105 setAccessRevoked ( false ) ;
106+ setUnknownQueueWarning ( null ) ;
102107 }
103108 } ) ;
104109
@@ -221,6 +226,7 @@ export default function App() {
221226
222227 const handleUnknownQueueLoaded = ( items : UnknownIngredientQueueItem [ ] ) : void => {
223228 setUnknownIngredientQueue ( items ) ;
229+ setUnknownQueueWarning ( null ) ;
224230 hasLoadedUnknownQueue = true ;
225231 markInitialViewReady ( ) ;
226232 } ;
@@ -256,6 +262,70 @@ export default function App() {
256262 membershipProbeResult,
257263 } ) ;
258264
265+ const probeUnknownQueuePlainRead = async ( ) : Promise < UnknownQueueReadProbeResult > => {
266+ try {
267+ await getDocs ( collection ( db , `households/${ resolved . householdId } /unknownIngredientQueue` ) ) ;
268+ return 'succeeded' ;
269+ } catch ( probeError ) {
270+ const parsedProbeError = toFirestoreListenerErrorInfo ( probeError ) ;
271+ if ( isFirestorePermissionDeniedError ( parsedProbeError ) ) {
272+ return 'permission-denied' ;
273+ }
274+
275+ console . error ( 'unknown_queue_plain_read_probe_failed' , {
276+ error : probeError ,
277+ householdId : resolved . householdId ,
278+ code : parsedProbeError . code ,
279+ message : parsedProbeError . message ,
280+ projectId : firebaseConfig . projectId ,
281+ databaseId : firebaseConfig . firestoreDatabaseId ,
282+ buildId : appBuildId ,
283+ uid : user . uid ,
284+ email : user . email ?? null ,
285+ path : unknownQueuePath ,
286+ targetFingerprint,
287+ membershipProbeResult,
288+ } ) ;
289+ return 'failed' ;
290+ }
291+ } ;
292+
293+ const applyUnknownQueueFailure = async (
294+ parsedError : ReturnType < typeof toFirestoreListenerErrorInfo > ,
295+ logLabel : 'unknown_queue_snapshot_failed' | 'unknown_queue_snapshot_fallback_failed' ,
296+ error : unknown ,
297+ ) : Promise < void > => {
298+ const plainReadProbeResult = isFirestorePermissionDeniedError ( parsedError )
299+ ? await probeUnknownQueuePlainRead ( )
300+ : 'not-run' ;
301+ const classification = classifyUnknownQueueLoadFailure ( {
302+ error : parsedError ,
303+ membershipProbeResult,
304+ plainReadProbeResult,
305+ } ) ;
306+
307+ console . error ( logLabel , {
308+ error,
309+ householdId : resolved . householdId ,
310+ code : parsedError . code ,
311+ message : parsedError . message ,
312+ diagnosticKind : classification . diagnosticKind ,
313+ plainReadProbeResult,
314+ projectId : firebaseConfig . projectId ,
315+ databaseId : firebaseConfig . firestoreDatabaseId ,
316+ buildId : appBuildId ,
317+ uid : user . uid ,
318+ email : user . email ?? null ,
319+ path : unknownQueuePath ,
320+ targetFingerprint,
321+ membershipProbeResult,
322+ } ) ;
323+
324+ setUnknownQueueWarning ( appendBuildIdToDiagnosticMessage ( classification . userMessage , appBuildId ) ) ;
325+ hasLoadedUnknownQueue = true ;
326+ markInitialViewReady ( ) ;
327+ } ;
328+
259329 const subscribeUnknownQueueFallback = ( ) : void => {
260330 if ( unknownQueueFallbackUnsub !== null ) {
261331 return ;
@@ -272,29 +342,7 @@ export default function App() {
272342 } ,
273343 ( error ) => {
274344 const parsedError = toFirestoreListenerErrorInfo ( error ) ;
275- console . error ( 'unknown_queue_snapshot_fallback_failed' , {
276- error,
277- householdId : resolved . householdId ,
278- code : parsedError . code ,
279- message : parsedError . message ,
280- projectId : firebaseConfig . projectId ,
281- databaseId : firebaseConfig . firestoreDatabaseId ,
282- buildId : appBuildId ,
283- uid : user . uid ,
284- email : user . email ?? null ,
285- path : unknownQueuePath ,
286- targetFingerprint,
287- membershipProbeResult,
288- } ) ;
289- setUiFeedback ( {
290- kind : 'error' ,
291- message : appendBuildIdToDiagnosticMessage (
292- getUnknownQueueLoadErrorMessage ( parsedError , membershipProbeResult ) ,
293- appBuildId ,
294- ) ,
295- } ) ;
296- hasLoadedUnknownQueue = true ;
297- markInitialViewReady ( ) ;
345+ void applyUnknownQueueFailure ( parsedError , 'unknown_queue_snapshot_fallback_failed' , error ) ;
298346 } ,
299347 ) ;
300348 } ;
@@ -310,46 +358,17 @@ export default function App() {
310358 } ,
311359 ( error ) => {
312360 const parsedError = toFirestoreListenerErrorInfo ( error ) ;
313- console . error ( 'unknown_queue_snapshot_failed' , {
314- error,
315- householdId : resolved . householdId ,
316- code : parsedError . code ,
317- message : parsedError . message ,
318- projectId : firebaseConfig . projectId ,
319- databaseId : firebaseConfig . firestoreDatabaseId ,
320- buildId : appBuildId ,
321- uid : user . uid ,
322- email : user . email ?? null ,
323- path : unknownQueuePath ,
324- targetFingerprint,
325- membershipProbeResult,
326- } ) ;
327-
328361 if ( isFirestoreFailedPreconditionError ( parsedError ) ) {
329362 if ( unknownQueueUnsub !== null ) {
330363 unknownQueueUnsub ( ) ;
331364 unknownQueueUnsub = null ;
332365 }
333- setUiFeedback ( {
334- kind : 'error' ,
335- message : appendBuildIdToDiagnosticMessage (
336- getUnknownQueueLoadErrorMessage ( parsedError , membershipProbeResult ) ,
337- appBuildId ,
338- ) ,
339- } ) ;
366+ setUnknownQueueWarning ( appendBuildIdToDiagnosticMessage ( 'Review queue order is temporarily unavailable.' , appBuildId ) ) ;
340367 subscribeUnknownQueueFallback ( ) ;
341368 return ;
342369 }
343370
344- setUiFeedback ( {
345- kind : 'error' ,
346- message : appendBuildIdToDiagnosticMessage (
347- getUnknownQueueLoadErrorMessage ( parsedError , membershipProbeResult ) ,
348- appBuildId ,
349- ) ,
350- } ) ;
351- hasLoadedUnknownQueue = true ;
352- markInitialViewReady ( ) ;
371+ void applyUnknownQueueFailure ( parsedError , 'unknown_queue_snapshot_failed' , error ) ;
353372 } ,
354373 ) ;
355374 } catch ( error ) {
@@ -799,6 +818,7 @@ export default function App() {
799818 onClearAnomaly = { handleClearAnomaly }
800819 logs = { logs }
801820 unknownIngredientQueue = { unknownIngredientQueue }
821+ unknownQueueWarning = { unknownQueueWarning }
802822 onPromoteUnknownIngredient = { handlePromoteUnknownIngredient }
803823 onDismissUnknownIngredient = { handleDismissUnknownIngredient }
804824 language = { ownerLanguage }
0 commit comments