Repository navigation
Develop #264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop #264
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,7 @@ import { Platform } from 'react-native'; | |
|
|
||
| import { getPresence, uploadAttachment } from '@/api/chat/chat'; | ||
| import { AckBanner } from '@/components/chat/ack-banner'; | ||
| import { copyToClipboard, getChannelDisplayName, getImageMimeType } from '@/components/chat/chat-utils'; | ||
| import { buildGifMetadata, buildLocationMetadata, copyToClipboard, getChannelDisplayName, getImageMimeType } from '@/components/chat/chat-utils'; | ||
| import { GifPickerSheet } from '@/components/chat/gif-picker-sheet'; | ||
| import { MessageActionsSheet } from '@/components/chat/message-actions-sheet'; | ||
| import { MessageBubble } from '@/components/chat/message-bubble'; | ||
|
|
@@ -155,7 +155,7 @@ export default function ChannelConversationScreen() { | |
| const handleSendGif = useCallback( | ||
| (gif: GifResultData) => { | ||
| if (!channelId) return; | ||
| const metadata = JSON.stringify({ GifUrl: gif.GifUrl, PreviewUrl: gif.PreviewUrl, Width: gif.Width, Height: gif.Height, Title: gif.Title }); | ||
| const metadata = buildGifMetadata(gif); | ||
| void useChatStore.getState().sendMessage({ channelId, body: gif.Title ?? 'GIF', messageType: ChatMessageType.Gif, metadataJson: metadata }); | ||
| }, | ||
| [channelId] | ||
|
|
@@ -164,7 +164,7 @@ export default function ChannelConversationScreen() { | |
| const handleSendLocation = useCallback( | ||
| (latitude: number, longitude: number, urgent: boolean) => { | ||
| if (!channelId) return; | ||
| const metadata = JSON.stringify({ Latitude: latitude, Longitude: longitude }); | ||
| const metadata = buildLocationMetadata(latitude, longitude); | ||
| void useChatStore.getState().sendMessage({ | ||
| channelId, | ||
| body: t('chat.shared_location'), | ||
|
|
@@ -312,7 +312,9 @@ export default function ChannelConversationScreen() { | |
| ) : ( | ||
| <FlatList | ||
| data={ordered} | ||
| maintainVisibleContentPosition={{ startRenderingFromBottom: true }} | ||
| // autoscrollToBottomThreshold is off by default in FlashList v2; without it a | ||
| // sent/incoming message lands below the viewport, hidden behind the composer. | ||
| maintainVisibleContentPosition={{ startRenderingFromBottom: true, autoscrollToBottomThreshold: 0.2 }} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnamed magic number for the Kody rule violation: Replace magic numbers with named constants Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| keyExtractor={keyExtractor} | ||
| renderItem={renderItem} | ||
| onStartReached={handleStartReached} | ||
|
|
@@ -354,7 +356,7 @@ export default function ChannelConversationScreen() { | |
| handleToggleReaction( | ||
| m, | ||
| emoji, | ||
| m.Reactions.some((r) => r.Emoji === emoji && r.UserId === currentUserId) | ||
| (m.Reactions ?? []).some((r) => r.Emoji === emoji && r.UserId === currentUserId) | ||
| ) | ||
| } | ||
| onReply={openThread} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -64,12 +64,55 @@ export function parseMetadata<T>(metadataJson?: string | null): T | null { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * MetadataJson wire contract, shared with the web client: a nested, camelCase envelope | ||
| * — `{ location: { latitude, longitude, label } }` and `{ gif: { url, previewUrl, width, | ||
| * height } }`. Earlier mobile builds wrote a flat PascalCase object that the web client | ||
| * cannot read (it falls back to rendering the message body), so the builders below emit | ||
| * only the nested form while the parsers still accept the flat one for history already | ||
| * stored on the server. | ||
| */ | ||
| function readString(value: unknown): string | undefined { | ||
| return typeof value === 'string' && value.length > 0 ? value : undefined; | ||
| } | ||
|
|
||
| function readNumber(value: unknown): number | undefined { | ||
| return typeof value === 'number' && Number.isFinite(value) ? value : undefined; | ||
| } | ||
|
|
||
| export function buildLocationMetadata(latitude: number, longitude: number, label?: string): string { | ||
| return JSON.stringify({ location: { latitude, longitude, label } }); | ||
| } | ||
|
|
||
| export function buildGifMetadata(gif: { GifUrl: string; PreviewUrl?: string; Width?: number; Height?: number }): string { | ||
| return JSON.stringify({ gif: { url: gif.GifUrl, previewUrl: gif.PreviewUrl, width: gif.Width, height: gif.Height } }); | ||
| } | ||
|
|
||
| export function parseLocationMetadata(metadataJson?: string | null): ChatLocationMetadata | null { | ||
| return parseMetadata<ChatLocationMetadata>(metadataJson); | ||
| const raw = parseMetadata<Record<string, unknown>>(metadataJson); | ||
| if (!raw) return null; | ||
| const nested = (raw.location ?? raw.Location) as Record<string, unknown> | undefined; | ||
| const source = nested ?? raw; | ||
| const latitude = readNumber(source.latitude ?? source.Latitude); | ||
| const longitude = readNumber(source.longitude ?? source.Longitude); | ||
| if (latitude === undefined || longitude === undefined) return null; | ||
| return { Latitude: latitude, Longitude: longitude, Label: readString(source.label ?? source.Label) }; | ||
| } | ||
|
|
||
| export function parseGifMetadata(metadataJson?: string | null): ChatGifMetadata | null { | ||
| return parseMetadata<ChatGifMetadata>(metadataJson); | ||
| const raw = parseMetadata<Record<string, unknown>>(metadataJson); | ||
| if (!raw) return null; | ||
| const nested = (raw.gif ?? raw.Gif) as Record<string, unknown> | undefined; | ||
| const source = nested ?? raw; | ||
| const url = readString(source.url ?? source.Url ?? source.gifUrl ?? source.GifUrl); | ||
| if (!url) return null; | ||
| return { | ||
| GifUrl: url, | ||
| PreviewUrl: readString(source.previewUrl ?? source.PreviewUrl), | ||
| Width: readNumber(source.width ?? source.Width), | ||
| Height: readNumber(source.height ?? source.Height), | ||
| Title: readString(source.title ?? source.Title), | ||
| }; | ||
|
Comment on lines
+83
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline src/components/chat/chat-utils.ts --items all
ast-grep outline src/components/chat/__tests__/chat-utils.test.ts --items all
rg -n -C 3 \
'build(Location|Gif)Metadata|parse(Location|Gif)Metadata|legacy|NaN|Infinity|MetadataJson' \
src/components/chat/__tests__/chat-utils.test.tsRepository: Resgrid/Unit Length of output: 2791 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== chat-utils.ts relevant code =="
sed -n '50,122p' src/components/chat/chat-utils.ts | cat -n -v
echo
echo "== chat-utils test imports and content =="
sed -n '1,140p' src/components/chat/__tests__/chat-utils.test.ts | cat -n -v
echo
echo "== metadata metadata contract tests across repo =="
rg -n -C 4 \
'buildLocationMetadata|buildGifMetadata|parseLocationMetadata|parseGifMetadata|parseImageMetadata|parseMetadata|buildImageMetadata' \
src/components/chat/__tests__ src --glob '*.{test,Ts,ts,tsx}' || true
echo
echo "== tests using readNumber/readString/error/null return edge cases =="
rg -n -C 3 \
'NaN|Infinity|malform|invalid.coordinates|missing|metadataJson|MetadataJson|readNumber|readString' \
src/components/chat/__tests__ src --glob '*.{test,Ts,ts,tsx}' || trueRepository: Resgrid/Unit Length of output: 50368 Add Jest coverage for the metadata wire contract.
🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| } | ||
|
|
||
| export function parseImageMetadata(metadataJson?: string | null): ChatImageMetadata | null { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance impact from inline arrow functions in JSX props, which create new function instances on every render. Move the
onToggleReactiondefinition outside the render method.Kody rule violation: Avoid using .bind() or arrow functions in JSX props
Prompt for LLM
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.