chore: update deps and add TypeScript null safety checks

This commit is contained in:
2026-01-04 06:44:41 +00:00
parent d786a7463b
commit 619fcdb9ae
26 changed files with 3232 additions and 1581 deletions

View File

@@ -0,0 +1,18 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v22.21.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-11-30T10_05_40_911Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-11-30T10_05_40_911Z-debug-0.log
11 silly logfile done cleaning log files
12 verbose cwd /home/notebrook/backend
13 verbose os Linux 6.8.0-85-generic
14 verbose node v22.21.0
15 verbose npm v11.6.2
16 verbose exit 1
17 verbose code 1

View File

@@ -0,0 +1,19 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v22.21.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-11-30T10_05_42_270Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-11-30T10_05_42_270Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files
13 verbose cwd /home/notebrook/backend
14 verbose os Linux 6.8.0-85-generic
15 verbose node v22.21.0
16 verbose npm v11.6.2
17 verbose exit 1
18 verbose code 1

View File

@@ -0,0 +1,19 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-02T18_08_09_047Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-02T18_08_09_047Z-debug-0.log
11 silly logfile start cleaning logs, removing 2 files
12 silly logfile done cleaning log files
13 verbose cwd /home/notebrook/backend
14 verbose os Linux 6.8.0-87-generic
15 verbose node v25.2.0
16 verbose npm v11.6.2
17 verbose exit 1
18 verbose code 1

View File

@@ -0,0 +1,13 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/frontend-vue/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm run dev --host
8 verbose argv "run" "dev" "--" "--host"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-02T18_08_09_048Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-02T18_08_09_048Z-debug-0.log
11 silly logfile start cleaning logs, removing 2 files
12 silly logfile done cleaning log files

View File

@@ -0,0 +1,19 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-02T18_08_11_876Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-02T18_08_11_876Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files
13 verbose cwd /home/notebrook/backend
14 verbose os Linux 6.8.0-87-generic
15 verbose node v25.2.0
16 verbose npm v11.6.2
17 verbose exit 1
18 verbose code 1

View File

@@ -0,0 +1,19 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-02T18_08_13_400Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-02T18_08_13_400Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files
13 verbose cwd /home/notebrook/backend
14 verbose os Linux 6.8.0-87-generic
15 verbose node v25.2.0
16 verbose npm v11.6.2
17 verbose exit 1
18 verbose code 1

View File

@@ -0,0 +1,19 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-02T18_08_14_897Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-02T18_08_14_897Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files
13 verbose cwd /home/notebrook/backend
14 verbose os Linux 6.8.0-87-generic
15 verbose node v25.2.0
16 verbose npm v11.6.2
17 verbose exit 1
18 verbose code 1

View File

@@ -0,0 +1,19 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-02T18_08_16_430Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-02T18_08_16_430Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files
13 verbose cwd /home/notebrook/backend
14 verbose os Linux 6.8.0-87-generic
15 verbose node v25.2.0
16 verbose npm v11.6.2
17 verbose exit 1
18 verbose code 1

View File

@@ -0,0 +1,13 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.4
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-02T18_28_20_942Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-02T18_28_20_942Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files

View File

@@ -0,0 +1,13 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.4
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-04T06_22_30_604Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-04T06_22_30_604Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files

View File

@@ -0,0 +1,13 @@
0 verbose cli /usr/bin/node /usr/bin/npm
1 info using npm@11.6.2
2 info using node@v25.2.0
3 silly config load:file:/usr/lib/node_modules/npm/npmrc
4 silly config load:file:/home/notebrook/backend/.npmrc
5 silly config load:file:/home/notebrook/.npmrc
6 silly config load:file:/usr/etc/npmrc
7 verbose title npm start
8 verbose argv "start"
9 verbose logfile logs-max:10 dir:/home/notebrook/.npm/_logs/2025-12-07T09_24_41_497Z-
10 verbose logfile /home/notebrook/.npm/_logs/2025-12-07T09_24_41_497Z-debug-0.log
11 silly logfile start cleaning logs, removing 1 files
12 silly logfile done cleaning log files

View File

2022
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,22 +14,22 @@
"typescript": "^5.5.4" "typescript": "^5.5.4"
}, },
"dependencies": { "dependencies": {
"@types/better-sqlite3": "^7.6.11", "@types/better-sqlite3": "^7.6.13",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.19",
"@types/express": "^4.17.21", "@types/express": "^5.0.6",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.10",
"@types/multer": "^1.4.11", "@types/multer": "^2.0.0",
"@types/ws": "^8.5.12", "@types/ws": "^8.18.1",
"better-sqlite3": "^11.2.1", "better-sqlite3": "^12.5.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^17.2.3",
"express": "^4.19.2", "express": "^5.2.1",
"multer": "^1.4.5-lts.1", "multer": "^2.0.2",
"ollama": "^0.5.8", "ollama": "^0.6.3",
"openai": "^4.56.0", "openai": "^6.9.1",
"selfsigned": "^2.4.1", "selfsigned": "^5.2.0",
"sharp": "^0.33.5", "sharp": "^0.34.5",
"tsx": "^4.18.0", "tsx": "^4.21.0",
"ws": "^8.18.0" "ws": "^8.18.3"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -12,27 +12,27 @@
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"vue": "^3.5.13", "vue": "^3.5.25",
"vue-router": "^4.4.5", "vue-router": "^4.6.3",
"pinia": "^2.3.0", "pinia": "^3.0.4",
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.2",
"@vueuse/core": "^11.3.0", "@vueuse/core": "^14.1.0",
"@vueuse/sound": "^2.0.1" "@vueuse/sound": "^2.1.3"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.10.2", "@types/node": "^24.10.1",
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^6.0.2",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.8.1",
"typescript": "^5.7.2", "typescript": "^5.9.3",
"vite": "^6.0.5", "vite": "^7.2.6",
"vue-tsc": "^2.1.10", "vue-tsc": "^3.1.5",
"vite-plugin-pwa": "^0.21.2", "vite-plugin-pwa": "^1.2.0",
"@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/eslint-plugin": "^8.48.1",
"@typescript-eslint/parser": "^8.18.2", "@typescript-eslint/parser": "^8.48.1",
"@vue/eslint-config-prettier": "^10.1.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.1.3", "@vue/eslint-config-typescript": "^14.6.0",
"eslint": "^9.17.0", "eslint": "^9.39.1",
"eslint-plugin-vue": "^9.32.0", "eslint-plugin-vue": "^10.6.2",
"prettier": "^3.4.2" "prettier": "^3.7.3"
} }
} }

View File

@@ -108,12 +108,12 @@ const trapFocus = (event: KeyboardEvent) => {
if (event.shiftKey) { if (event.shiftKey) {
if (document.activeElement === firstElement) { if (document.activeElement === firstElement) {
event.preventDefault() event.preventDefault()
lastElement.focus() lastElement?.focus()
} }
} else { } else {
if (document.activeElement === lastElement) { if (document.activeElement === lastElement) {
event.preventDefault() event.preventDefault()
firstElement.focus() firstElement?.focus()
} }
} }
} }

View File

@@ -241,7 +241,7 @@ onMounted(() => {
const getFocusedMessage = (): ExtendedMessage | UnsentMessage | null => { const getFocusedMessage = (): ExtendedMessage | UnsentMessage | null => {
const messages = allMessages.value const messages = allMessages.value
if (focusedMessageIndex.value >= 0 && focusedMessageIndex.value < messages.length) { if (focusedMessageIndex.value >= 0 && focusedMessageIndex.value < messages.length) {
return messages[focusedMessageIndex.value] return messages[focusedMessageIndex.value] ?? null
} }
return null return null
} }

View File

@@ -207,8 +207,8 @@ const switchCamera = async () => {
// Determine if this is likely a front camera // Determine if this is likely a front camera
const currentCamera = availableCameras.value[currentCameraIndex.value] const currentCamera = availableCameras.value[currentCameraIndex.value]
isFrontCamera.value = currentCamera.label.toLowerCase().includes('front') || isFrontCamera.value = currentCamera?.label.toLowerCase().includes('front') ||
currentCamera.label.toLowerCase().includes('user') currentCamera?.label.toLowerCase().includes('user') || false
try { try {
await startCamera() await startCamera()

View File

@@ -281,7 +281,10 @@ const performDelete = async () => {
// Switch to first available channel if we were on the deleted channel // Switch to first available channel if we were on the deleted channel
if (appStore.currentChannelId === props.channel.id && appStore.channels.length > 0) { if (appStore.currentChannelId === props.channel.id && appStore.channels.length > 0) {
await appStore.setCurrentChannel(appStore.channels[0].id) const firstChannel = appStore.channels[0]
if (firstChannel) {
await appStore.setCurrentChannel(firstChannel.id)
}
} }
} catch (error) { } catch (error) {
// For delete, we can't do offline fallback easily since it affects server state // For delete, we can't do offline fallback easily since it affects server state

View File

@@ -142,7 +142,7 @@ const uploadFiles = async () => {
// For single file, use the filename as message content // For single file, use the filename as message content
// For multiple files, show count // For multiple files, show count
const messageContent = selectedFiles.value.length === 1 const messageContent = selectedFiles.value.length === 1
? selectedFiles.value[0].name ? selectedFiles.value[0]?.name || 'Uploaded file'
: `Uploaded ${selectedFiles.value.length} files` : `Uploaded ${selectedFiles.value.length} files`
// Create a message first to attach files to // Create a message first to attach files to
@@ -151,6 +151,10 @@ const uploadFiles = async () => {
// Upload the first file (backend uses single file per message) // Upload the first file (backend uses single file per message)
const file = selectedFiles.value[0] const file = selectedFiles.value[0]
if (!file) {
throw new Error('No file selected')
}
try { try {
const uploadedFile = await apiService.uploadFile(appStore.currentChannelId, message.id, file) const uploadedFile = await apiService.uploadFile(appStore.currentChannelId, message.id, file)
uploadProgress.value[0] = 100 uploadProgress.value[0] = 100

View File

@@ -161,11 +161,17 @@ const handleAlphanumericNavigation = (char: string, currentIndex: number) => {
focusChannel(nextMatch) focusChannel(nextMatch)
} else { } else {
// Wrap around to the first match // Wrap around to the first match
focusChannel(matchingIndices[0]) const firstMatch = matchingIndices[0]
if (firstMatch !== undefined) {
focusChannel(firstMatch)
}
} }
} else { } else {
// New character: jump to first match // New character: jump to first match
focusChannel(matchingIndices[0]) const firstMatch = matchingIndices[0]
if (firstMatch !== undefined) {
focusChannel(firstMatch)
}
} }
} }

View File

@@ -174,14 +174,20 @@ export function useAudio() {
console.log(`playWater called - global: ${globalWaterSounds.length}, reactive: ${waterSounds.value.length} water sounds available`) console.log(`playWater called - global: ${globalWaterSounds.length}, reactive: ${waterSounds.value.length} water sounds available`)
if (globalWaterSounds.length > 0) { if (globalWaterSounds.length > 0) {
const randomIndex = Math.floor(Math.random() * globalWaterSounds.length) const randomIndex = Math.floor(Math.random() * globalWaterSounds.length)
await playSoundBuffer(globalWaterSounds[randomIndex]) const buffer = globalWaterSounds[randomIndex]
if (buffer) {
await playSoundBuffer(buffer)
}
} else { } else {
console.warn('Water sounds not loaded - trying to load them now') console.warn('Water sounds not loaded - trying to load them now')
if (globalAudioContext) { if (globalAudioContext) {
await loadAllSounds() await loadAllSounds()
if (globalWaterSounds.length > 0) { if (globalWaterSounds.length > 0) {
const randomIndex = Math.floor(Math.random() * globalWaterSounds.length) const randomIndex = Math.floor(Math.random() * globalWaterSounds.length)
await playSoundBuffer(globalWaterSounds[randomIndex]) const buffer = globalWaterSounds[randomIndex]
if (buffer) {
await playSoundBuffer(buffer)
}
} }
} }
} }
@@ -190,7 +196,10 @@ export function useAudio() {
const playSent = async () => { const playSent = async () => {
if (globalSentSounds.length > 0) { if (globalSentSounds.length > 0) {
const randomIndex = Math.floor(Math.random() * globalSentSounds.length) const randomIndex = Math.floor(Math.random() * globalSentSounds.length)
await playSoundBuffer(globalSentSounds[randomIndex]) const buffer = globalSentSounds[randomIndex]
if (buffer) {
await playSoundBuffer(buffer)
}
} else { } else {
console.warn('Sent sounds not loaded') console.warn('Sent sounds not loaded')
} }
@@ -303,7 +312,7 @@ export function useAudio() {
// Select default voice (prefer English voices) // Select default voice (prefer English voices)
if (!selectedVoice.value && voices.length > 0) { if (!selectedVoice.value && voices.length > 0) {
const englishVoice = voices.find(voice => voice.lang.startsWith('en')) const englishVoice = voices.find(voice => voice.lang.startsWith('en'))
selectedVoice.value = englishVoice || voices[0] selectedVoice.value = englishVoice || voices[0] || null
} }
} }

View File

@@ -136,8 +136,15 @@ export function useWebSocket() {
const channels = [...appStore.channels] const channels = [...appStore.channels]
const channelIndex = channels.findIndex(c => c.id === channelId) const channelIndex = channels.findIndex(c => c.id === channelId)
if (channelIndex !== -1) { if (channelIndex !== -1) {
channels[channelIndex] = { ...channels[channelIndex], name: data.name } const existingChannel = channels[channelIndex]
appStore.setChannels(channels) if (existingChannel) {
channels[channelIndex] = {
id: existingChannel.id,
name: data.name,
created_at: existingChannel.created_at
}
appStore.setChannels(channels)
}
} }
} }

View File

@@ -73,11 +73,16 @@ export const useAppStore = defineStore('app', () => {
} }
const channelMessages = messages.value[message.channel_id] const channelMessages = messages.value[message.channel_id]
if (!channelMessages) return
const existingIndex = channelMessages.findIndex(m => m.id === message.id) const existingIndex = channelMessages.findIndex(m => m.id === message.id)
if (existingIndex !== -1) { if (existingIndex !== -1) {
// Upsert: update existing to avoid duplicates from WebSocket vs sync // Upsert: update existing to avoid duplicates from WebSocket vs sync
channelMessages[existingIndex] = { ...channelMessages[existingIndex], ...message } const existingMessage = channelMessages[existingIndex]
if (existingMessage) {
channelMessages[existingIndex] = { ...existingMessage, ...message }
}
} else { } else {
channelMessages.push(message) channelMessages.push(message)
} }
@@ -93,9 +98,14 @@ export const useAppStore = defineStore('app', () => {
const updateMessage = (messageId: number, updates: Partial<ExtendedMessage>) => { const updateMessage = (messageId: number, updates: Partial<ExtendedMessage>) => {
for (const channelId in messages.value) { for (const channelId in messages.value) {
const channelMessages = messages.value[parseInt(channelId)] const channelMessages = messages.value[parseInt(channelId)]
if (!channelMessages) continue
const messageIndex = channelMessages.findIndex(m => m.id === messageId) const messageIndex = channelMessages.findIndex(m => m.id === messageId)
if (messageIndex !== -1) { if (messageIndex !== -1) {
channelMessages[messageIndex] = { ...channelMessages[messageIndex], ...updates } const existingMessage = channelMessages[messageIndex]
if (existingMessage) {
channelMessages[messageIndex] = { ...existingMessage, ...updates }
}
break break
} }
} }
@@ -108,6 +118,8 @@ export const useAppStore = defineStore('app', () => {
const removeMessage = (messageId: number) => { const removeMessage = (messageId: number) => {
for (const channelId in messages.value) { for (const channelId in messages.value) {
const channelMessages = messages.value[parseInt(channelId)] const channelMessages = messages.value[parseInt(channelId)]
if (!channelMessages) continue
const messageIndex = channelMessages.findIndex(m => m.id === messageId) const messageIndex = channelMessages.findIndex(m => m.id === messageId)
if (messageIndex !== -1) { if (messageIndex !== -1) {
channelMessages.splice(messageIndex, 1) channelMessages.splice(messageIndex, 1)
@@ -127,6 +139,11 @@ export const useAppStore = defineStore('app', () => {
} }
const message = sourceMessages[messageIndex] const message = sourceMessages[messageIndex]
if (!message) {
console.warn(`Message ${messageId} not found at index ${messageIndex}`)
return
}
sourceMessages.splice(messageIndex, 1) sourceMessages.splice(messageIndex, 1)
// Update message's channel_id and add to target channel // Update message's channel_id and add to target channel
@@ -137,6 +154,8 @@ export const useAppStore = defineStore('app', () => {
} }
const targetMessages = messages.value[targetChannelId] const targetMessages = messages.value[targetChannelId]
if (!targetMessages) return
targetMessages.push(updatedMessage) targetMessages.push(updatedMessage)
// Keep chronological order in target channel // Keep chronological order in target channel

View File

@@ -443,6 +443,11 @@ const announceLastMessage = (position: number) => {
} }
const message = messages[messageIndex] const message = messages[messageIndex]
if (!message) {
toastStore.info('No message is available in this position')
return
}
const timeStr = formatTimestampForScreenReader(message.created_at) const timeStr = formatTimestampForScreenReader(message.created_at)
const announcement = `${message.content}; sent ${timeStr}` const announcement = `${message.content}; sent ${timeStr}`
@@ -608,7 +613,10 @@ onMounted(async () => {
// 5. Auto-select first channel if none selected and we have channels // 5. Auto-select first channel if none selected and we have channels
if (!appStore.currentChannelId && appStore.channels.length > 0) { if (!appStore.currentChannelId && appStore.channels.length > 0) {
await selectChannel(appStore.channels[0].id) const firstChannel = appStore.channels[0]
if (firstChannel) {
await selectChannel(firstChannel.id)
}
} }
// 6. Auto-focus message input on page load // 6. Auto-focus message input on page load