+ {/* Row 3: Message Input (auto height). pb keeps the 1.5rem base but
+ extends past the iOS home indicator via the safe-area inset (#30
+ fix #1; needs viewport-fit=cover, set in the root layout). */}
+
{
}
});
});
+
+describe('resolveParticipantName (#30 fix #5)', () => {
+ it('returns Unknown User on a query error (transient — do not mislabel)', () => {
+ expect(resolveParticipantName(null, true)).toBe('Unknown User');
+ expect(resolveParticipantName({ display_name: 'Ada' }, true)).toBe(
+ 'Unknown User'
+ );
+ });
+
+ it('returns Deleted User when the profile row is null (no error)', () => {
+ expect(resolveParticipantName(null, false)).toBe('Deleted User');
+ });
+
+ it('prefers display_name, then username, for a present profile', () => {
+ expect(
+ resolveParticipantName({ display_name: 'Ada', username: 'ada99' }, false)
+ ).toBe('Ada');
+ expect(
+ resolveParticipantName({ display_name: null, username: 'ada99' }, false)
+ ).toBe('ada99');
+ expect(
+ resolveParticipantName({ display_name: '', username: '' }, false)
+ ).toBe('Unknown User');
+ });
+});
diff --git a/src/components/organisms/ConversationView/ConversationView.tsx b/src/components/organisms/ConversationView/ConversationView.tsx
index 4c699295..be31e265 100644
--- a/src/components/organisms/ConversationView/ConversationView.tsx
+++ b/src/components/organisms/ConversationView/ConversationView.tsx
@@ -13,6 +13,22 @@ import type { DecryptedMessage } from '@/types/messaging';
const logger = createLogger('organisms:ConversationView');
+/**
+ * Resolve the display name for the other 1-to-1 participant, distinguishing
+ * three cases (#30 fix #5):
+ * - query failed (transient/RLS) → 'Unknown User' (neutral; don't mislabel)
+ * - no profile row (null) → 'Deleted User' (account genuinely gone)
+ * - profile present → display_name || username || 'Unknown User'
+ */
+export function resolveParticipantName(
+ profile: { username?: string | null; display_name?: string | null } | null,
+ hadError: boolean
+): string {
+ if (hadError) return 'Unknown User';
+ if (!profile) return 'Deleted User';
+ return profile.display_name || profile.username || 'Unknown User';
+}
+
export interface ConversationViewProps {
/** Conversation to display. The component owns all message state internally;
* changing this prop resets and reloads. */
@@ -121,13 +137,10 @@ export default function ConversationView({
if (profileError) {
logger.warn('Profile query error', { error: profileError.message });
- setParticipantName('Unknown User');
- return;
}
-
- setParticipantName(
- profile?.display_name || profile?.username || 'Unknown User'
- );
+ // Distinguish deleted account (null row) from a transient query error and
+ // from a present-but-unnamed profile (#30 fix #5).
+ setParticipantName(resolveParticipantName(profile, !!profileError));
} catch (err) {
logger.warn('Error loading participant info', { error: err });
setParticipantName('Unknown User');