Skip to content

fix: address deepsource diagnostics across monorepo#60

Merged
vi70x4 merged 1 commit into
mainfrom
fix/deepsource-i18n-cleanup
Jun 24, 2026
Merged

fix: address deepsource diagnostics across monorepo#60
vi70x4 merged 1 commit into
mainfrom
fix/deepsource-i18n-cleanup

Conversation

@vi70x4

@vi70x4 vi70x4 commented Jun 24, 2026

Copy link
Copy Markdown
Member

Addresses DeepSource-reported issues across 118 files. Fixes JS-R1005, JS-0116, JS-0321, JS-W1028, JS-0323, JS-0388, JS-0715, JS-R1004, JS-W1041, JS-C1001, JS-0608, JS-0045, JS-0051, JS-0102, JS-0077, JS-W1029, JS-0322, JS-0327, JS-0362. Also reverts JS-W1028 changes where barrel re-exports use named exports instead of default. pnpm typecheck passes.

@deepsource-io

deepsource-io Bot commented Jun 24, 2026

Copy link
Copy Markdown

DeepSource Code Review

We reviewed changes in 6adf1ed...a131bc6 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

Important

Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Jun 24, 2026 10:19a.m. Review ↗
Shell Jun 24, 2026 10:19a.m. Review ↗
C & C++ Jun 24, 2026 10:19a.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

type ResolveResourceKeys<
// eslint-disable-next-line ts/no-empty-object-type
Schema extends Record<string, any> = {},
Schema extends Record<string, unknown> = {},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The `{}` ("empty object") type allows any non-nullish value, including literals like `0` and `""`. - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. - If you want a type meaning "any object", you probably want `object` instead. - If you want a type meaning "any value", you probably want `unknown` instead


An empty interface is equivalent to its supertype. If the interface does not implement a supertype, then the interface is equivalent to an empty object ({}). In both cases it can be omitted.

Schema extends Record<string, unknown> = {},
// eslint-disable-next-line ts/no-empty-object-type
DefineLocaleMessageSchema extends Record<string, any> = {},
DefineLocaleMessageSchema extends Record<string, unknown> = {},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The `{}` ("empty object") type allows any non-nullish value, including literals like `0` and `""`. - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. - If you want a type meaning "any object", you probably want `object` instead. - If you want a type meaning "any value", you probably want `unknown` instead


An empty interface is equivalent to its supertype. If the interface does not implement a supertype, then the interface is equivalent to an empty object ({}). In both cases it can be omitted.

interface TranslationFunction<
// eslint-disable-next-line ts/no-empty-object-type
Schema extends Record<string, any> = {},
Schema extends Record<string, unknown> = {},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The `{}` ("empty object") type allows any non-nullish value, including literals like `0` and `""`. - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. - If you want a type meaning "any object", you probably want `object` instead. - If you want a type meaning "any value", you probably want `unknown` instead


An empty interface is equivalent to its supertype. If the interface does not implement a supertype, then the interface is equivalent to an empty object ({}). In both cases it can be omitted.

Schema extends Record<string, unknown> = {},
// eslint-disable-next-line ts/no-empty-object-type
DefineLocaleMessageSchema extends Record<string, any> = {},
DefineLocaleMessageSchema extends Record<string, unknown> = {},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The `{}` ("empty object") type allows any non-nullish value, including literals like `0` and `""`. - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. - If you want a type meaning "any object", you probably want `object` instead. - If you want a type meaning "any value", you probably want `unknown` instead


An empty interface is equivalent to its supertype. If the interface does not implement a supertype, then the interface is equivalent to an empty object ({}). In both cases it can be omitted.

if (pendingHeadlessRequests.has(fingerprint)) {
log.log(`[Headless] Deduplicating identical request: ${params.prompt.slice(0, 30)}...`)
return pendingHeadlessRequests.get(fingerprint)!
async function executeHeadlessRequest(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`executeHeadlessRequest` has a cyclomatic complexity of 14 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

Comment on lines +82 to +94
async function setCardActiveBackgroundId(cardStore: ReturnType<typeof useAiriCardStore>, entryId: string) {
const cardId = cardStore.activeCardId
if (!cardId) return

const card = cardStore.cards.get(cardId)
if (!card) return

const extension = JSON.parse(JSON.stringify(card.extensions || {}))
if (!extension.airi) extension.airi = {}
if (!extension.airi.modules) extension.airi.modules = {}
extension.airi.modules.activeBackgroundId = entryId
cardStore.updateCard(cardId, { ...card, extensions: extension })
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found `async` function without any `await` expressions


A function that does not contain any await expressions should not be async (except for some edge cases in TypeScript which are discussed below). Asynchronous functions in JavaScript behave differently than other functions in two important ways:

}
}

async function setCardActiveBackgroundId(cardStore: ReturnType<typeof useAiriCardStore>, entryId: string) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`setCardActiveBackgroundId` has a cyclomatic complexity of 6 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

}
}

async function executeCreateImageJournalEntry(params: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`executeCreateImageJournalEntry` has a cyclomatic complexity of 22 with "high" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

return match?.key
}

function resolveWeatherIconKey(): string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`resolveWeatherIconKey` has a cyclomatic complexity of 8 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.


// ── Private: memory enrichment ───────────────────────────────────────

private async enrichContextWithMemory(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`enrichContextWithMemory` has a cyclomatic complexity of 6 with "medium" risk


A function with high cyclomatic complexity can be hard to understand and
maintain. Cyclomatic complexity is a software metric that measures the number of
independent paths through a function. A higher cyclomatic complexity indicates
that the function has more decision points and is more complex.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request performs a comprehensive cleanup of DeepSource diagnostics, refactoring complex functions, replacing 'any' types with 'unknown', and improving null/async safety across multiple packages. While these changes significantly improve code quality, several critical issues must be addressed: a major bug in the local socket client where the buffer is not updated with unconsumed bytes (causing duplicate message processing and memory leaks), a missing await on card updates in the image journal store, a potential memory leak in the WebSocket client due to un-cleared connection timeouts and abort listeners, potential TypeError crashes in the Ollama provider settings, and a type-safety regression in IconAnimation.vue. Additionally, a raw tool execution log file was accidentally committed and should be removed.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 186 to +189
socket.on('data', (chunk: Buffer) => {
buffer = Buffer.concat([buffer, chunk])
expectedLength = this.processSocketData(buffer, socket, expectedLength)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

CRITICAL BUG: The refactored processSocketData method processes the buffer but never updates the outer buffer variable with the remaining unconsumed bytes. As a result, the outer buffer will grow indefinitely, and previously processed messages will be parsed repeatedly on every new 'data' event, leading to duplicate message handling, parsing errors, and memory leaks.\n\nWe should update processSocketData to return both the remaining buffer and the next expected length as a tuple, and reassign buffer in the event handler.

    socket.on('data', (chunk: Buffer) => {
      buffer = Buffer.concat([buffer, chunk])
      const [remaining, nextExpected] = this.processSocketData(buffer, socket, expectedLength)
      buffer = remaining
      expectedLength = nextExpected
    })

Comment on lines +200 to +227
private processSocketData(buffer: Buffer, socket: Socket, expectedLength: number | null): number | null {
let currentExpected = expectedLength
let currentBuffer = buffer

if (buffer.length < expectedLength) break
while (true) {
if (currentExpected === null) {
if (currentBuffer.length < HEADER_SIZE) return null

const messageBytes = buffer.subarray(0, expectedLength)
buffer = buffer.subarray(expectedLength)
expectedLength = null
currentExpected = currentBuffer.readUInt32BE(0)

try {
const json = messageBytes.toString('utf-8')
const parsed = JSON.parse(json) as IpcMessage

// Auto-respond to pings with pong.
if (parsed.type === 'ping') {
this.send({
id: generateId(),
type: 'pong',
timestamp: new Date().toISOString(),
correlationId: parsed.id,
}).catch(() => {
// Best-effort: ignore send errors on pong.
})
}

for (const handler of this.messageHandlers) {
try {
handler(parsed)
} catch (error) {
_logger('[LocalSocketClient] Message handler threw:', error)
}
}
} catch (error) {
_logger(
'[LocalSocketClient] Failed to parse message:',
error instanceof Error ? error.message : String(error),
)
if (currentExpected > MAX_MESSAGE_SIZE) {
_logger(`[LocalSocketClient] Message too large (${currentExpected} bytes), disconnecting.`)
socket.destroy()
return null
}

currentBuffer = currentBuffer.subarray(HEADER_SIZE)
}
})

socket.on('close', () => {
this.socket = null
this.stopHeartbeat()
if (currentBuffer.length < currentExpected) return currentExpected

const messageBytes = currentBuffer.subarray(0, currentExpected)
currentBuffer = currentBuffer.subarray(currentExpected)
currentExpected = null

this.handleRawMessage(messageBytes)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Update processSocketData to return a tuple containing the remaining unconsumed buffer and the next expected length, ensuring the outer buffer state is correctly maintained.

  private processSocketData(buffer: Buffer, socket: Socket, expectedLength: number | null): [Buffer, number | null] {
    let currentExpected = expectedLength
    let currentBuffer = buffer

    while (true) {
      if (currentExpected === null) {
        if (currentBuffer.length < HEADER_SIZE) return [currentBuffer, null]

        currentExpected = currentBuffer.readUInt32BE(0)

        if (currentExpected > MAX_MESSAGE_SIZE) {
          _logger(`[LocalSocketClient] Message too large (${currentExpected} bytes), disconnecting.`)
          socket.destroy()
          return [Buffer.alloc(0), null]
        }

        currentBuffer = currentBuffer.subarray(HEADER_SIZE)
      }

      if (currentBuffer.length < currentExpected) return [currentBuffer, currentExpected]

      const messageBytes = currentBuffer.subarray(0, currentExpected)
      currentBuffer = currentBuffer.subarray(currentExpected)
      currentExpected = null

      this.handleRawMessage(messageBytes)
    }
  }

if (!extension.airi) extension.airi = {}
if (!extension.airi.modules) extension.airi.modules = {}
extension.airi.modules.activeBackgroundId = entryId
cardStore.updateCard(cardId, { ...card, extensions: extension })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The cardStore.updateCard method is asynchronous but is not awaited here. Since setCardActiveBackgroundId is marked async and is awaited by its callers, not awaiting updateCard inside it creates a floating promise, which can lead to race conditions or unhandled rejections.

Suggested change
cardStore.updateCard(cardId, { ...card, extensions: extension })
await cardStore.updateCard(cardId, { ...card, extensions: extension })

Comment on lines +576 to 618
private createConnectionRacePromise(
reject: (reason: Error) => void,
timeout?: number,
abortSignal?: AbortSignal,
): void {
if (timeout && timeout > 0) {
const handle = setTimeout(() => {
reject(new Error(`Connection timed out after ${timeout}ms`))
}, timeout)
handle.unref?.()
}

const timeout = options?.timeout
if (typeof timeout !== 'undefined' && timeout <= 0) {
throw new Error(`Connection timed out after ${timeout}ms`)
if (abortSignal) {
const onAbort = () => reject(new Error('Connection aborted'))
abortSignal.addEventListener('abort', onAbort, { once: true })
}
}

const abortSignal = options?.abortSignal
if (abortSignal?.aborted) {
private validateConnectionOptions(options?: ConnectOptions): void {
if (options?.timeout && options.timeout <= 0) {
throw new Error(`Connection timed out after ${options.timeout}ms`)
}

if (options?.abortSignal?.aborted) {
throw new Error('Connection aborted')
}
}

let timeoutHandle: ReturnType<typeof setTimeout> | undefined
let removeAbortListener: (() => void) | undefined
// eslint-disable-next-line consistent-return
private async waitForConnection(connectPromise: Promise<void>, options?: ConnectOptions) {
if (!options?.timeout && !options?.abortSignal) {
return connectPromise
}

try {
await Promise.race([
connectPromise,
new Promise<void>((_, reject) => {
if (typeof timeout !== 'undefined') {
timeoutHandle = setTimeout(() => {
reject(new Error(`Connection timed out after ${timeout}ms`))
}, timeout)
}

if (abortSignal) {
const onAbort = () => {
reject(new Error('Connection aborted'))
}

abortSignal.addEventListener('abort', onAbort, { once: true })
removeAbortListener = () => abortSignal.removeEventListener('abort', onAbort)
}
}),
])
} finally {
if (timeoutHandle) {
clearTimeout(timeoutHandle)
}
this.validateConnectionOptions(options)

removeAbortListener?.()
}
await Promise.race([
connectPromise,
new Promise<void>((_, reject) => {
this.createConnectionRacePromise(reject, options?.timeout, options?.abortSignal)
}),
])
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

POTENTIAL MEMORY LEAK & UN-CLEARED RESOURCES: In the refactored waitForConnection and createConnectionRacePromise, when the connection succeeds (the happy path), the setTimeout timer is never cleared, and the abortSignal event listener is never removed.\n\nThis is a regression from the original implementation which properly cleared the timeout and removed the abort listener in a finally block. Leaving the abort listener active creates a reference closure that can prevent the Client instance from being garbage collected, leading to memory leaks.\n\nWe should refactor this to ensure that both the timer and the event listener are cleaned up regardless of whether the connection succeeds, times out, or is aborted.

  private validateConnectionOptions(options?: ConnectOptions): void {
    if (options?.timeout && options.timeout <= 0) {
      throw new Error(`Connection timed out after ${options.timeout}ms`)
    }

    if (options?.abortSignal?.aborted) {
      throw new Error('Connection aborted')
    }
  }

  // eslint-disable-next-line consistent-return
  private async waitForConnection(connectPromise: Promise<void>, options?: ConnectOptions) {
    if (!options?.timeout && !options?.abortSignal) {
      return connectPromise
    }

    this.validateConnectionOptions(options)

    let timeoutHandle: ReturnType<typeof setTimeout> | undefined
    let onAbort: (() => void) | undefined

    try {
      await Promise.race([
        connectPromise,
        new Promise<void>((_, reject) => {
          if (options?.timeout && options.timeout > 0) {
            timeoutHandle = setTimeout(() => {
              reject(new Error(`Connection timed out after ${options.timeout}ms`))
            }, options.timeout)
            timeoutHandle.unref?.()
          }

          if (options?.abortSignal) {
            onAbort = () => reject(new Error('Connection aborted'))
            options.abortSignal.addEventListener('abort', onAbort, { once: true })
          }
        }),
      ])
    } finally { 
      if (timeoutHandle) {
        clearTimeout(timeoutHandle)
      }
      if (options?.abortSignal && onAbort) {
        options.abortSignal.removeEventListener('abort', onAbort)
      }
    }
  }

Comment on lines +143 to +150
function initHeaders() {
if (!providers.value[providerId]?.headers) {
providers.value[providerId].headers = {}
}
if (headers.value.length === 0) {
headers.value = [{ key: '', value: '' }]
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If providers.value[providerId] is undefined, the optional chaining check !providers.value[providerId]?.headers will evaluate to true, but then assigning providers.value[providerId].headers = {} will throw a TypeError: Cannot set properties of undefined. We should defensively initialize the provider object first if it is not guaranteed to exist.

function initHeaders() {
  if (!providers.value[providerId]) {
    providers.value[providerId] = {}
  }
  if (!providers.value[providerId].headers) {
    providers.value[providerId].headers = {}
  }
  if (headers.value.length === 0) {
    headers.value = [{ key: '', value: '' }]
  }
}

Comment on lines +152 to +156
function initThinkingMode() {
if (!providers.value[providerId].thinkingMode) {
providers.value[providerId].thinkingMode = 'auto'
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Defensively check if providers.value[providerId] exists before accessing thinkingMode to prevent potential runtime TypeError crashes.

function initThinkingMode() {
  if (!providers.value[providerId]) {
    providers.value[providerId] = {}
  }
  if (!providers.value[providerId].thinkingMode) {
    providers.value[providerId].thinkingMode = 'auto'
  }
}

@@ -0,0 +1,654 @@
╭─── Claude Code v2.1.186 ─────────────────────────────────────────────────────╮

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This session log file appears to have been committed accidentally. Raw tool execution logs should generally not be committed to the repository. Consider removing this file and adding such log patterns to your .gitignore.

const emit = defineEmits<{
(e: 'animationEnded'): void
}>()
const emit = defineEmits(['animationEnded'])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Changing the strongly-typed defineEmits to an untyped array-based defineEmits reduces type safety. If this was done to resolve a DeepSource warning about call signatures (JS-0362), you can use the modern Vue 3.3+ type-safe syntax instead, which avoids call signatures while preserving full type safety.

const emit = defineEmits<{
  animationEnded: []
}>()

- JS-R1005: reduce cyclomatic complexity via extraction and guard clauses
- JS-0116: remove spurious async keywords, add missing await
- JS-0321: annotate intentional empty functions, remove dead empty funcs
- JS-W1028: fix default imports (revert where module lacks default export)
- JS-0323: replace any with proper types or unknown
- JS-0388: refactor i18n function overloading
- JS-0715: remove unused component properties
- JS-R1004: simplify useless template literals
- JS-W1041: extract complex boolean returns into named variables
- JS-C1001: simplify complex import statements
- JS-0608: remove duplicate field definitions
- JS-0045: add consistent return paths
- JS-0051: add hasOwnProperty guards in for-in loops
- JS-0102: replace var with const/let
- JS-0077: fix assignments to native/global objects
- JS-W1029: replace deprecated API usage
- JS-0322: remove or document empty interfaces
- JS-0327: convert namespace-only classes to plain objects
- JS-0362: replace interface call signatures with type aliases

Also fixes type errors induced by JS-W1028 changes where barrel
re-exports use 'export { default as Name }' pattern (named, not default).
SpeechProvider type narrowed to SpeechProviderWithExtraOptions in
stage-pages speech providers.
@vi70x4 vi70x4 force-pushed the fix/deepsource-i18n-cleanup branch from 93919ac to a131bc6 Compare June 24, 2026 10:19
@vi70x4 vi70x4 merged commit 6d3535f into main Jun 24, 2026
5 of 9 checks passed
@vi70x4 vi70x4 deleted the fix/deepsource-i18n-cleanup branch June 29, 2026 21:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants