Skip to content

Quality hardening: real bug fixes + SOLID/DRY refactors + honest coverage + tooling spine#46

Open
alichherawalla wants to merge 336 commits into
mainfrom
chore/quality-hardening
Open

Quality hardening: real bug fixes + SOLID/DRY refactors + honest coverage + tooling spine#46
alichherawalla wants to merge 336 commits into
mainfrom
chore/quality-hardening

Conversation

@alichherawalla

@alichherawalla alichherawalla commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What this is

The chore/quality-hardening branch: fix the real bugs, land the SOLID/DRY refactor
backlog, bring coverage to an honest floor, and stand up the code-quality tooling spine.
One PR per repo (pro is a separate repo — companion PR in desktop-pro, same branch name).

🐞 Real bugs fixed (each with a regression test)

  • 3 divergent ext→MIME maps unified into src/main/mime.ts — fixed a .webp attachment
    mislabelled image/jpeg (vision model may reject) and gifimage/png.
  • Upload picker vs router drift — the file-dialog allowlist now derives from the router's
    classify sets (was omitting gif/bmp/heic/opus/avi the router handles).
  • Double image-intent decision (root of the image-gen-as-tool bug) — the renderer no longer
    pre-decides when the agentic tool path owns the turn.
  • (from the earlier coverage work on this branch) image-gen params single-source, reasoning
    survives reload, KV-cache choice survives restart.

Refactors (SOLID/DRY — behavior-neutral, tested)

ToolDef.run uniform dispatch (DIP); one mimeForExt; appNameLikeClause (7 rag:chat sites);
likeMatch/LIKE_COLUMNS (facet counts == results); epochMsSql; shared isValidGguf,
artifact-kind/model-kind label maps, CHAT_JOB/IMAGE_JOB, ffmpeg-decode, streamCompletion
(one SSE transport), parseSqliteUtc, parseSessionId; ChatList link cyan→emerald token.

Coverage — honest floor, enforced

all:true + the true testable-surface include/exclude (native/IPC/spawn shells excluded, each
covered by e2e/smoke/test:db). Ratchet floor in vitest.config.ts, blocked on pre-push AND CI.
Now 97.1 / 92.1 / 96.0 / 98.1 (stmts/branches/funcs/lines). README carries the badges.

Quality-tooling spine (new)

  • dependency-cruiser (blocking CI) — open-core boundary (core ↛ pro) + pure/IO isolation +
    circular/unresolvable/dev-dep/orphans. 0 violations.
  • SonarCloud Automatic Analysis on core (content: bugs/smells/duplication).
  • tsc dead-code gates (both configs) — noUnusedLocals/noUnusedParameters/allowUnreachableCode:false.
  • knip (npm run knip) — module-graph dead code; first pass deleted 27 unused template UI files.
  • typed no-unnecessary-condition (warn-ratchet) — dead-branch detection.

Evidence

  • npx tsc --noEmit — both tsconfig.node.json and tsconfig.web.json: 0 errors.
  • npm run test:coverage2110 passed, 1 skipped; floor held (97.1/92.1/96.0/98.1).
  • npm run depcruise0 violations.
  • Diffstat: 139 files, +7689 / −4654.

Honest gaps (not blocking this PR)

  • Functional/on-device verification not yet run this cycle — the gates prove the tested surface
    (regression safety) + structural health; they do NOT verify end-to-end functionality (real model
    chat/image-gen, connectors, capture). Recommend the Playwright e2e tour + a live golden-path smoke
    before release. Screenshots therefore pending the e2e run.
  • Deferred backlog tracked in docs/CODE_AUDIT.md (now a clean "what's left" list): ImageRuntime,
    imagegen/ipc/database SRP splits, tabler→Phosphor, and the warn-ratchet lint backlog
    (no-unnecessary-condition 289, sonarjs-on-pro 295, knip deps/exports). Each lands as its own
    verified change — not this PR.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Image generation now keeps per-model steps/size overrides across model changes and restarts.
    • “Thinking” reasoning is persisted and restored after reloads.
    • Upload routing supports more file formats and keeps file-picker allowlists in sync.
    • Tool streaming now returns more structured citations/sources and defers image generation via requests.
    • Artifact/model/connectors labels are more consistent across the UI.
  • Bug Fixes
    • Correct MIME handling for WebP and other image types; improved Content-Type for served/captured content.
    • Image intent routing better respects active tools/connectors and avoids unwanted auto-routing.
    • Pressing Enter in the identity panel now saves changes.
  • Tests
    • Expanded automated coverage across uploads, search, licensing, streaming, image generation, and persistence.

Summary by Gitar

  • Tooling & Infrastructure:
    • Integrated dependency-cruiser for architectural boundary enforcement and knip for dead-code elimination.
    • Added comprehensive automated test coverage for persistence, streaming, and tool interaction logic.
  • Core Logic Refactors:
    • Unified MIME type handling for image attachments and standardized session ID parsing logic.
    • Extracted IPC query logic into ipc-query-logic.ts and refined tool dispatch using a uniform ToolResult structure.
  • LLM Engine Hardening:
    • Implemented a fresh-connection-per-request policy (agent: false) for http.request to prevent ECONNRESET in multi-round tool loops.
    • Added an userExplicit pin-set for LLM settings to ensure user-selected performance modes and cache configurations persist across restarts.
  • Composer & State Persistence:
    • Enabled persistence for reasoning blocks, per-model image parameter overrides, and global image generation settings.
    • Fixed image-gen state drift by ensuring the composer and model-picker share a single source of truth for active model selection.
  • Type Safety:
    • Enabled noUncheckedIndexedAccess across the project, resulting in 14/14 module migrations and associated guard logic.

This will update automatically on new commits.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 563 files, which is 463 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e44d68dc-f6cb-4257-97e1-a3ac9a9cb7b9

📥 Commits

Reviewing files that changed from the base of the PR and between 9f09181 and 755f23b.

⛔ Files ignored due to path filters (22)
  • docs/RELEASE_TEST_CHECKLIST.csv is excluded by !**/*.csv
  • package-lock.json is excluded by !**/package-lock.json
  • packages/clipboard/dist/adapters/electron.d.mts is excluded by !**/dist/**
  • packages/clipboard/dist/adapters/electron.d.ts is excluded by !**/dist/**
  • packages/clipboard/dist/index.d.mts is excluded by !**/dist/**
  • packages/clipboard/dist/index.d.ts is excluded by !**/dist/**
  • packages/clipboard/dist/index.js is excluded by !**/dist/**
  • packages/clipboard/dist/index.mjs is excluded by !**/dist/**
  • packages/clipboard/dist/types-B7DdTBPa.d.mts is excluded by !**/dist/**
  • packages/clipboard/dist/types-B7DdTBPa.d.ts is excluded by !**/dist/**
  • packages/models/dist/adapters/node.d.mts is excluded by !**/dist/**
  • packages/models/dist/adapters/node.d.ts is excluded by !**/dist/**
  • packages/models/dist/adapters/node.js is excluded by !**/dist/**
  • packages/models/dist/adapters/node.mjs is excluded by !**/dist/**
  • packages/models/dist/index.d.mts is excluded by !**/dist/**
  • packages/models/dist/index.d.ts is excluded by !**/dist/**
  • packages/models/dist/index.js is excluded by !**/dist/**
  • packages/models/dist/index.mjs is excluded by !**/dist/**
  • packages/models/dist/types-CQDbinZH.d.mts is excluded by !**/dist/**
  • packages/models/dist/types-CQDbinZH.d.ts is excluded by !**/dist/**
  • packages/rag/dist/index.js is excluded by !**/dist/**
  • packages/rag/dist/index.mjs is excluded by !**/dist/**
📒 Files selected for processing (598)
  • .agent/rules/working-style.md
  • .dependency-cruiser.cjs
  • .github/instructions.md
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .github/workflows/windows-build.yml
  • .prettierignore
  • CLAUDE.md
  • README.md
  • ROADMAP_DESKTOP.md
  • docs/API.md
  • docs/CHAT_UX_SPEC.md
  • docs/CODE_AUDIT.md
  • docs/CONSOLE_PLAN.md
  • docs/CONSOLIDATION_PLAN.md
  • docs/DESIGN.md
  • docs/DEVICE_TEST_LOG.md
  • docs/ENTERPRISE_BUILD_PLAN.md
  • docs/FEATURES.md
  • docs/FUNCTIONAL_TEST_STRATEGY.md
  • docs/GAPS_BACKLOG.md
  • docs/GATEWAY_SPINE.md
  • docs/IMAGE_GEN_OPTIMIZATION.md
  • docs/MARKETING_ANGLES.md
  • docs/MASTER_PLAN.md
  • docs/P0_P2_INTEGRATION_COVERAGE.md
  • docs/RELEASE_DESKTOP.md
  • docs/RELEASE_TEST_CHECKLIST.md
  • docs/SYNC_PLAN.md
  • docs/WINDOWS_SUPPORT.md
  • docs/WINDOWS_TEST_MODELS.md
  • docs/WINDOWS_TEST_PLAN.md
  • docs/features/gateway.md
  • e2e/chat-large-collection.spec.ts
  • e2e/chat-memory.spec.ts
  • e2e/desktop-polish.spec.ts
  • e2e/fixtures/fake-llama-server.mjs
  • e2e/fixtures/fake-sd-cli.mjs
  • e2e/onboarding-resume.spec.ts
  • e2e/pro.spec.ts
  • e2e/projects-layout.spec.ts
  • e2e/resilience-single-instance.spec.ts
  • e2e/settings-residency.spec.ts
  • e2e/smoke.spec.ts
  • e2e/tour.spec.ts
  • e2e/tts-speak.spec.ts
  • electron-builder.yml
  • electron.vite.config.ts
  • eslint.config.mjs
  • integration-tests/conversation-rename.dbtest.ts
  • integration-tests/manual-model-setup.test.ts
  • integration-tests/mcp-connector-setup.dbtest.ts
  • integration-tests/permission-recovery.test.ts
  • knip.json
  • package.json
  • packages/clipboard/package.json
  • packages/clipboard/src/adapters/electron.ts
  • packages/clipboard/src/engine.ts
  • packages/clipboard/src/fuzzy-search.ts
  • packages/clipboard/src/index.ts
  • packages/clipboard/src/types.ts
  • packages/design/package.json
  • packages/design/src/index.ts
  • packages/design/src/tokens.css
  • packages/design/tailwind-preset.js
  • packages/models/package.json
  • packages/models/src/adapters/node.ts
  • packages/models/src/catalog.ts
  • packages/models/src/credibility.ts
  • packages/models/src/download.ts
  • packages/models/src/filters.ts
  • packages/models/src/hf.ts
  • packages/models/src/imagegen.ts
  • packages/models/src/index.ts
  • packages/models/src/providers.ts
  • packages/models/src/quant.ts
  • packages/models/src/recommend-image.ts
  • packages/models/src/types.ts
  • packages/rag/package.json
  • packages/rag/src/bridges.ts
  • packages/rag/src/chunking.ts
  • packages/rag/src/extract.ts
  • packages/rag/src/index.ts
  • packages/rag/src/retrieval.ts
  • packages/rag/src/service.ts
  • packages/rag/src/tools.ts
  • packages/rag/src/types.ts
  • packages/rag/src/vectorMath.ts
  • playwright.config.ts
  • pr-targets.local.md
  • resources/tts-worker.mjs
  • scripts/blur-pro-shots.mjs
  • scripts/build-llama.sh
  • scripts/build-mac-local.sh
  • scripts/cap-orbit.mjs
  • scripts/download-mmproj.mjs
  • scripts/download-model.mjs
  • scripts/hooks/pre-push
  • scripts/lib/macos-artifact-integrity.d.mts
  • scripts/lib/macos-artifact-integrity.mjs
  • scripts/notify-slack-release.mjs
  • scripts/release-evidence-profile.mjs
  • scripts/release-notes.mjs
  • scripts/repro-chat.mjs
  • scripts/resign.js
  • scripts/screenshots-pro.mjs
  • scripts/screenshots.mjs
  • scripts/smoke-api.sh
  • scripts/smoke-dmg-install.sh
  • scripts/smoke-packaged.sh
  • scripts/smoke-test.mjs
  • scripts/test-db.sh
  • scripts/test-vision.mjs
  • scripts/verify-electron-builder-artifact.js
  • scripts/verify-macos-bundle.mjs
  • sonar-project.properties
  • src/bootstrap/globals.d.ts
  • src/bootstrap/proStub.ts
  • src/components/ui/focus-cards.tsx
  • src/hooks/use-outside-click.tsx
  • src/main/__tests__/active-models-io.test.ts
  • src/main/__tests__/active-models.test.ts
  • src/main/__tests__/api-docs.test.ts
  • src/main/__tests__/artifact-persistence.integration.test.ts
  • src/main/__tests__/audio-engines.integration.test.ts
  • src/main/__tests__/cache-cleanup.integration.test.ts
  • src/main/__tests__/calculator.test.ts
  • src/main/__tests__/chat-health.test.ts
  • src/main/__tests__/connector-delete-secrets.dbtest.ts
  • src/main/__tests__/conversation-delete-cascade.dbtest.ts
  • src/main/__tests__/data-privacy-delete-all.dbtest.ts
  • src/main/__tests__/database-integration.dbtest.ts
  • src/main/__tests__/db-test-runner.test.ts
  • src/main/__tests__/dmg-install-smoke.integration.test.ts
  • src/main/__tests__/downloaded-models-branches.test.ts
  • src/main/__tests__/downloaded-models.test.ts
  • src/main/__tests__/electron-builder-local-state.test.ts
  • src/main/__tests__/embeddings-cachedir.test.ts
  • src/main/__tests__/entity-admission-policy.test.ts
  • src/main/__tests__/entity-domain.dbtest.ts
  • src/main/__tests__/extract-prompt.test.ts
  • src/main/__tests__/fetch-win-binaries.test.ts
  • src/main/__tests__/files-classify.test.ts
  • src/main/__tests__/files-image-upload.dbtest.ts
  • src/main/__tests__/fixtures/mcp-reachable-server.mjs
  • src/main/__tests__/harness/fake-llama-server.ts
  • src/main/__tests__/harness/offline-fetch.ts
  • src/main/__tests__/image-default.test.ts
  • src/main/__tests__/image-defaults.test.ts
  • src/main/__tests__/image-runtime-reliability.integration.dbtest.ts
  • src/main/__tests__/ipc-query-logic.test.ts
  • src/main/__tests__/ipc-type-parity.test.ts
  • src/main/__tests__/kill-orphan-port.test.ts
  • src/main/__tests__/llama-error.test.ts
  • src/main/__tests__/llm-http-no-keepalive.test.ts
  • src/main/__tests__/macos-artifact-integrity.integration.test.ts
  • src/main/__tests__/mcp-connector-tool-extension.dbtest.ts
  • src/main/__tests__/mcp-parse-data-url.test.ts
  • src/main/__tests__/mcp-timeout.dbtest.ts
  • src/main/__tests__/media-permission.test.ts
  • src/main/__tests__/media-range.test.ts
  • src/main/__tests__/media-server.integration.test.ts
  • src/main/__tests__/mime.test.ts
  • src/main/__tests__/model-download-tts.integration.dbtest.ts
  • src/main/__tests__/model-port-ownership.integration.test.ts
  • src/main/__tests__/model-server-chat.integration.test.ts
  • src/main/__tests__/model-server-image.integration.dbtest.ts
  • src/main/__tests__/model-sizing-branches.test.ts
  • src/main/__tests__/model-sizing.test.ts
  • src/main/__tests__/ocr-helper.integration.test.ts
  • src/main/__tests__/ogcapture-serve.test.ts
  • src/main/__tests__/p0-p2-coverage-ledger.test.ts
  • src/main/__tests__/packaged-helpers.integration.test.ts
  • src/main/__tests__/parser.test.ts
  • src/main/__tests__/product-identity.test.ts
  • src/main/__tests__/project-delete-cascade.dbtest.ts
  • src/main/__tests__/prompts.test.ts
  • src/main/__tests__/pure-branch-fills.test.ts
  • src/main/__tests__/rag-conversations-scope.dbtest.ts
  • src/main/__tests__/rag-empty-memory.dbtest.ts
  • src/main/__tests__/rag-ipc-project-create.dbtest.ts
  • src/main/__tests__/rag-store-integration.dbtest.ts
  • src/main/__tests__/reasoning-persistence.dbtest.ts
  • src/main/__tests__/recommend-image.test.ts
  • src/main/__tests__/release-packaging.integration.test.ts
  • src/main/__tests__/residency-integration.test.ts
  • src/main/__tests__/retry.test.ts
  • src/main/__tests__/runtime-env.test.ts
  • src/main/__tests__/runtime-manager.test.ts
  • src/main/__tests__/runtime-residency-io.test.ts
  • src/main/__tests__/runtime-residency.test.ts
  • src/main/__tests__/script-log-safety.test.ts
  • src/main/__tests__/sd-server.test.ts
  • src/main/__tests__/search-ranking.test.ts
  • src/main/__tests__/settings-consumers.dbtest.ts
  • src/main/__tests__/settings-persistence.dbtest.ts
  • src/main/__tests__/settings-store.test.ts
  • src/main/__tests__/skills-parse.test.ts
  • src/main/__tests__/storage-usage.integration.dbtest.ts
  • src/main/__tests__/stream-abort-entry.test.ts
  • src/main/__tests__/stream-guards.test.ts
  • src/main/__tests__/tool-content.test.ts
  • src/main/__tests__/tools-loop.dbtest.ts
  • src/main/__tests__/tools-parsers.test.ts
  • src/main/__tests__/tools-search.dbtest.ts
  • src/main/__tests__/tools-stream.test.ts
  • src/main/__tests__/tools-vision.dbtest.ts
  • src/main/__tests__/tts-logic.test.ts
  • src/main/__tests__/tts-spawn-contract.test.ts
  • src/main/__tests__/update-check.integration.dbtest.ts
  • src/main/__tests__/updater-publish.test.ts
  • src/main/__tests__/updater.test.ts
  • src/main/__tests__/vectors-predicates.test.ts
  • src/main/__tests__/vision-low-disk.integration.test.ts
  • src/main/active-models-logic.ts
  • src/main/active-models.ts
  • src/main/api-docs.ts
  • src/main/artifact-preview-ipc.ts
  • src/main/artifact-preview.test.ts
  • src/main/artifact-preview.ts
  • src/main/artifacts.ts
  • src/main/bootstrap/__tests__/hookRegistry.test.ts
  • src/main/bootstrap/__tests__/proEnabled.test.ts
  • src/main/bootstrap/hookRegistry.ts
  • src/main/bootstrap/loadProFeaturesMain.ts
  • src/main/bootstrap/pro-activation.ts
  • src/main/cache-cleanup.ts
  • src/main/calculator.ts
  • src/main/chat-health.ts
  • src/main/data-privacy.ts
  • src/main/database.ts
  • src/main/dev-seed.ts
  • src/main/downloaded-models.ts
  • src/main/embeddings.ts
  • src/main/entity-admission-policy.ts
  • src/main/entity-domain.ts
  • src/main/files-classify.ts
  • src/main/files.ts
  • src/main/image-default.ts
  • src/main/imagegen.ts
  • src/main/imagegen/__tests__/args.test.ts
  • src/main/imagegen/__tests__/generation-lifecycle.test.ts
  • src/main/imagegen/__tests__/memory-guard.test.ts
  • src/main/imagegen/__tests__/model-filter.test.ts
  • src/main/imagegen/__tests__/owned-path.integration.test.ts
  • src/main/imagegen/__tests__/progress.test.ts
  • src/main/imagegen/__tests__/runtime-detect.test.ts
  • src/main/imagegen/args.ts
  • src/main/imagegen/generation-lifecycle.ts
  • src/main/imagegen/memory-guard.ts
  • src/main/imagegen/model-filter.ts
  • src/main/imagegen/owned-path.ts
  • src/main/imagegen/progress.ts
  • src/main/imagegen/runtime-detect.ts
  • src/main/index.ts
  • src/main/ipc-query-logic.ts
  • src/main/ipc.ts
  • src/main/kill-orphan-port.ts
  • src/main/lib/retry.ts
  • src/main/license-ipc.ts
  • src/main/licensing/__tests__/device-fingerprint.test.ts
  • src/main/licensing/__tests__/keygen-parse.test.ts
  • src/main/licensing/__tests__/license-logic.test.ts
  • src/main/licensing/device-fingerprint.ts
  • src/main/licensing/keygen-client.ts
  • src/main/licensing/keygen-config.ts
  • src/main/licensing/license-service.ts
  • src/main/llama-error.ts
  • src/main/llm.ts
  • src/main/llm/__tests__/chat-payload.test.ts
  • src/main/llm/__tests__/gen-params.test.ts
  • src/main/llm/__tests__/http-post.integration.test.ts
  • src/main/llm/__tests__/kv-launch-roundtrip.test.ts
  • src/main/llm/__tests__/resource-mode.integration.test.ts
  • src/main/llm/__tests__/response-limit.integration.test.ts
  • src/main/llm/__tests__/response-result.test.ts
  • src/main/llm/__tests__/settings-math.test.ts
  • src/main/llm/__tests__/settings-merge.test.ts
  • src/main/llm/__tests__/sse-stream.test.ts
  • src/main/llm/__tests__/stream.test.ts
  • src/main/llm/chat-payload.ts
  • src/main/llm/gen-params.ts
  • src/main/llm/http-post.ts
  • src/main/llm/response-result.ts
  • src/main/llm/settings-math.ts
  • src/main/llm/sse-stream.ts
  • src/main/llm/stream.ts
  • src/main/mcp-ipc.ts
  • src/main/mcp-oauth.ts
  • src/main/mcp-parse-data-url.ts
  • src/main/mcp-server.ts
  • src/main/mcp.ts
  • src/main/media-permission.ts
  • src/main/media-range.ts
  • src/main/media-roots.ts
  • src/main/media-server.ts
  • src/main/mflux.ts
  • src/main/mime.ts
  • src/main/modality-queue/__tests__/jobs.test.ts
  • src/main/modality-queue/__tests__/policy.test.ts
  • src/main/modality-queue/__tests__/queue-branches.test.ts
  • src/main/modality-queue/__tests__/queue.test.ts
  • src/main/modality-queue/policy.ts
  • src/main/modality-queue/queue.ts
  • src/main/model-server.ts
  • src/main/model-server/__tests__/async-request.test.ts
  • src/main/model-server/__tests__/chat-messages.test.ts
  • src/main/model-server/__tests__/data-url.test.ts
  • src/main/model-server/__tests__/dimensions.test.ts
  • src/main/model-server/__tests__/errors.test.ts
  • src/main/model-server/__tests__/health.test.ts
  • src/main/model-server/__tests__/models-list.test.ts
  • src/main/model-server/__tests__/multipart.test.ts
  • src/main/model-server/__tests__/proxy-response.test.ts
  • src/main/model-server/async-request.ts
  • src/main/model-server/chat-messages.ts
  • src/main/model-server/data-url.ts
  • src/main/model-server/dimensions.ts
  • src/main/model-server/errors.ts
  • src/main/model-server/health.ts
  • src/main/model-server/models-list.ts
  • src/main/model-server/multipart.ts
  • src/main/model-server/proxy-response.ts
  • src/main/model-sizing.ts
  • src/main/models-manager.ts
  • src/main/models/__tests__/catalog-logic.test.ts
  • src/main/models/__tests__/download-error.test.ts
  • src/main/models/__tests__/download-pump.test.ts
  • src/main/models/__tests__/download-queue.test.ts
  • src/main/models/__tests__/download-verify.test.ts
  • src/main/models/__tests__/gguf.test.ts
  • src/main/models/__tests__/model-download-matrix.integration.test.ts
  • src/main/models/__tests__/model-integrity.integration.test.ts
  • src/main/models/__tests__/set-active-modal-choice.test.ts
  • src/main/models/__tests__/setup-logic.test.ts
  • src/main/models/catalog-logic.ts
  • src/main/models/download-error.ts
  • src/main/models/download-pump.ts
  • src/main/models/download-queue.ts
  • src/main/models/download-verify.ts
  • src/main/models/gguf.ts
  • src/main/models/setup-logic.ts
  • src/main/models/setup-types.ts
  • src/main/ocr.ts
  • src/main/ogcapture-serve.ts
  • src/main/parsers/__tests__/claude-desktop-parser.test.ts
  • src/main/parsers/__tests__/parser-branches.test.ts
  • src/main/parsers/chatgpt.ts
  • src/main/parsers/claude-desktop.ts
  • src/main/parsers/claude-web.ts
  • src/main/parsers/gemini.ts
  • src/main/parsers/index.ts
  • src/main/parsers/tagged-chat.ts
  • src/main/parsers/types.ts
  • src/main/permissions.ts
  • src/main/prompt-store.ts
  • src/main/prompts.ts
  • src/main/rag-ipc.ts
  • src/main/rag/__tests__/rag-prompt.test.ts
  • src/main/rag/extractors.ts
  • src/main/rag/index.ts
  • src/main/rag/prompt.ts
  • src/main/rag/store.ts
  • src/main/runtime-env.ts
  • src/main/runtime-manager.ts
  • src/main/runtime-residency-logic.ts
  • src/main/runtime-residency.ts
  • src/main/sd-server.ts
  • src/main/search-ranking.ts
  • src/main/search.ts
  • src/main/secrets.ts
  • src/main/settings-store.ts
  • src/main/setup.ts
  • src/main/skills-parse.ts
  • src/main/skills.ts
  • src/main/stream-guards.ts
  • src/main/tool-content.ts
  • src/main/tools-parsers.ts
  • src/main/tools.ts
  • src/main/tools/__tests__/mcpConnectorToolExtension.test.ts
  • src/main/tools/mcpConnectorToolExtension-logic.ts
  • src/main/tools/mcpConnectorToolExtension.ts
  • src/main/tools/tool-budget.ts
  • src/main/transcription/__tests__/bin-resolution.test.ts
  • src/main/transcription/__tests__/ffmpeg-decode.test.ts
  • src/main/transcription/__tests__/no-import-cycle.test.ts
  • src/main/transcription/__tests__/parakeet-cli.test.ts
  • src/main/transcription/__tests__/select.test.ts
  • src/main/transcription/__tests__/vad-edges.test.ts
  • src/main/transcription/__tests__/vad.test.ts
  • src/main/transcription/__tests__/wav.test.ts
  • src/main/transcription/__tests__/whisper-server.test.ts
  • src/main/transcription/bin-resolution.ts
  • src/main/transcription/classify.ts
  • src/main/transcription/ffmpeg-decode.ts
  • src/main/transcription/parakeet-cli.ts
  • src/main/transcription/select.ts
  • src/main/transcription/types.ts
  • src/main/transcription/vad.ts
  • src/main/transcription/wav.ts
  • src/main/transcription/whisper-cli.ts
  • src/main/transcription/whisper-server.ts
  • src/main/tts-logic.ts
  • src/main/tts.ts
  • src/main/updater.ts
  • src/main/vectors-predicates.ts
  • src/main/vectors.ts
  • src/main/vision.ts
  • src/preload/index.d.ts
  • src/preload/index.ts
  • src/renderer/index.html
  • src/renderer/src/App.tsx
  • src/renderer/src/__tests__/App.navigation.integration.test.tsx
  • src/renderer/src/__tests__/browser-boundaries.setup.ts
  • src/renderer/src/__tests__/theme.test.ts
  • src/renderer/src/assets/main.css
  • src/renderer/src/assets/onboarding.css
  • src/renderer/src/bootstrap/hookRegistry.ts
  • src/renderer/src/bootstrap/loadProFeaturesRenderer.ts
  • src/renderer/src/bootstrap/navRegistry.ts
  • src/renderer/src/bootstrap/proView.ts
  • src/renderer/src/bootstrap/screenRegistry.ts
  • src/renderer/src/bootstrap/sectionRegistry.ts
  • src/renderer/src/bootstrap/slotRegistry.ts
  • src/renderer/src/components/ArtifactCanvas.tsx
  • src/renderer/src/components/ChatDetail.tsx
  • src/renderer/src/components/ChatList.tsx
  • src/renderer/src/components/CommandPalette.tsx
  • src/renderer/src/components/ConnectorsScreen.tsx
  • src/renderer/src/components/ConversationTitleActions.tsx
  • src/renderer/src/components/DictationOverlay.tsx
  • src/renderer/src/components/GatewayScreen.tsx
  • src/renderer/src/components/MemoryChat.tsx
  • src/renderer/src/components/ModelPicker.tsx
  • src/renderer/src/components/ModelsScreen.tsx
  • src/renderer/src/components/Onboarding.tsx
  • src/renderer/src/components/PermissionGate.tsx
  • src/renderer/src/components/ProjectsScreen.tsx
  • src/renderer/src/components/Settings.tsx
  • src/renderer/src/components/SettingsCard.tsx
  • src/renderer/src/components/SettingsPanel.tsx
  • src/renderer/src/components/SkillsPanel.tsx
  • src/renderer/src/components/SourceFilterTabs.tsx
  • src/renderer/src/components/ThemeToggle.tsx
  • src/renderer/src/components/VoiceBubble.tsx
  • src/renderer/src/components/__tests__/ArtifactCanvas.preview.test.tsx
  • src/renderer/src/components/__tests__/DictationOverlay.integration.test.tsx
  • src/renderer/src/components/__tests__/MemoryChat.chat-lifecycle.test.tsx
  • src/renderer/src/components/__tests__/MemoryChat.clipboard-overlay.test.tsx
  • src/renderer/src/components/__tests__/MemoryChat.image.test.tsx
  • src/renderer/src/components/__tests__/MemoryChat.microphone-permission.integration.test.tsx
  • src/renderer/src/components/__tests__/MemoryChat.project-inheritance.test.tsx
  • src/renderer/src/components/__tests__/MemoryChat.reasoning.test.tsx
  • src/renderer/src/components/__tests__/MemoryChat.response-limit.integration.test.tsx
  • src/renderer/src/components/__tests__/MemoryChat.tool-image-cancel.test.tsx
  • src/renderer/src/components/__tests__/Settings.pro-sections.test.tsx
  • src/renderer/src/components/__tests__/Settings.update-check.test.tsx
  • src/renderer/src/components/__tests__/chatlist-brand.test.ts
  • src/renderer/src/components/__tests__/desktop-polish.integration.test.tsx
  • src/renderer/src/components/__tests__/harness/chat-boundary.tsx
  • src/renderer/src/components/__tests__/memorychat-image-gen-scope.test.ts
  • src/renderer/src/components/__tests__/memorychat-project-attribution.test.ts
  • src/renderer/src/components/__tests__/parseArtifact.test.ts
  • src/renderer/src/components/connectorCatalog.ts
  • src/renderer/src/components/pro/UpgradeScreen.tsx
  • src/renderer/src/components/pro/__tests__/UpgradeScreen.windows-notice.test.ts
  • src/renderer/src/components/pro/__tests__/proCatalog.nav.test.ts
  • src/renderer/src/components/pro/proCatalog.ts
  • src/renderer/src/components/pro/proSettingsCatalog.ts
  • src/renderer/src/components/setup/CacheCleanupControl.tsx
  • src/renderer/src/components/setup/DataPrivacyPanel.tsx
  • src/renderer/src/components/setup/HealthPanel.tsx
  • src/renderer/src/components/setup/SetupPanel.tsx
  • src/renderer/src/components/setup/StoragePanel.tsx
  • src/renderer/src/components/setup/__tests__/StorageUsage.integration.test.tsx
  • src/renderer/src/components/setup/__tests__/storage-format.test.ts
  • src/renderer/src/components/setup/storage-format.ts
  • src/renderer/src/components/ui/__tests__/orbiting-circles.test.tsx
  • src/renderer/src/components/ui/accordion.tsx
  • src/renderer/src/components/ui/animated-modal.tsx
  • src/renderer/src/components/ui/background-beams-with-collision.tsx
  • src/renderer/src/components/ui/background-gradient-animation.tsx
  • src/renderer/src/components/ui/badge.tsx
  • src/renderer/src/components/ui/bento-grid.tsx
  • src/renderer/src/components/ui/border-beam.tsx
  • src/renderer/src/components/ui/button.tsx
  • src/renderer/src/components/ui/canvas-reveal-effect.tsx
  • src/renderer/src/components/ui/card-spotlight.tsx
  • src/renderer/src/components/ui/collapsible.tsx
  • src/renderer/src/components/ui/colourful-text.tsx
  • src/renderer/src/components/ui/comet-card.tsx
  • src/renderer/src/components/ui/command.tsx
  • src/renderer/src/components/ui/dialog.tsx
  • src/renderer/src/components/ui/dropdown-menu.tsx
  • src/renderer/src/components/ui/flip-words.tsx
  • src/renderer/src/components/ui/focus-cards.tsx
  • src/renderer/src/components/ui/glare-card.tsx
  • src/renderer/src/components/ui/glowing-effect.tsx
  • src/renderer/src/components/ui/glowing-stars.tsx
  • src/renderer/src/components/ui/grid-backdrop.tsx
  • src/renderer/src/components/ui/hero-highlight.tsx
  • src/renderer/src/components/ui/hover-border-gradient.tsx
  • src/renderer/src/components/ui/item.tsx
  • src/renderer/src/components/ui/lamp.tsx
  • src/renderer/src/components/ui/orbiting-circles.tsx
  • src/renderer/src/components/ui/progressive-blur.tsx
  • src/renderer/src/components/ui/scroll-area.tsx
  • src/renderer/src/components/ui/select.tsx
  • src/renderer/src/components/ui/separator.tsx
  • src/renderer/src/components/ui/sheet.tsx
  • src/renderer/src/components/ui/shooting-stars.tsx
  • src/renderer/src/components/ui/sidebar.tsx
  • src/renderer/src/components/ui/sparkles.tsx
  • src/renderer/src/components/ui/spotlight.tsx
  • src/renderer/src/components/ui/starfield-backdrop.tsx
  • src/renderer/src/components/ui/stars-background.tsx
  • src/renderer/src/components/ui/tabs.tsx
  • src/renderer/src/components/ui/text-generate-effect.tsx
  • src/renderer/src/components/ui/textarea.tsx
  • src/renderer/src/components/ui/tooltip.tsx
  • src/renderer/src/components/ui/wobble-card.tsx
  • src/renderer/src/constants/links.ts
  • src/renderer/src/env.d.ts
  • src/renderer/src/hooks/NotificationProvider.tsx
  • src/renderer/src/hooks/ToastProvider.tsx
  • src/renderer/src/hooks/notification-state.ts
  • src/renderer/src/hooks/useNotifications.integration.test.tsx
  • src/renderer/src/hooks/useNotifications.ts
  • src/renderer/src/hooks/useNotifications.tsx
  • src/renderer/src/hooks/useReprocessing.tsx
  • src/renderer/src/hooks/useToast.test.tsx
  • src/renderer/src/hooks/useToast.ts
  • src/renderer/src/hooks/useToast.tsx
  • src/renderer/src/lib/__tests__/artifact-labels.test.ts
  • src/renderer/src/lib/__tests__/chat-history.test.ts
  • src/renderer/src/lib/__tests__/chat-labels.test.ts
  • src/renderer/src/lib/__tests__/chat-queue.test.ts
  • src/renderer/src/lib/__tests__/clipboard-write.test.ts
  • src/renderer/src/lib/__tests__/connectorCatalog.test.ts
  • src/renderer/src/lib/__tests__/device.test.ts
  • src/renderer/src/lib/__tests__/hookRegistry.test.ts
  • src/renderer/src/lib/__tests__/image-intent.test.ts
  • src/renderer/src/lib/__tests__/image-params-wiring.test.ts
  • src/renderer/src/lib/__tests__/image-params.test.ts
  • src/renderer/src/lib/__tests__/links.test.ts
  • src/renderer/src/lib/__tests__/message-persistence.test.ts
  • src/renderer/src/lib/__tests__/model-kind-labels.test.ts
  • src/renderer/src/lib/__tests__/navRegistry.test.ts
  • src/renderer/src/lib/__tests__/persist-toggle.test.ts
  • src/renderer/src/lib/__tests__/pro-navigation.test.ts
  • src/renderer/src/lib/__tests__/proCatalog.lookup.test.ts
  • src/renderer/src/lib/__tests__/proView.test.ts
  • src/renderer/src/lib/__tests__/screenRegistry.test.ts
  • src/renderer/src/lib/__tests__/sectionRegistry.test.ts
  • src/renderer/src/lib/__tests__/session-id.test.ts
  • src/renderer/src/lib/__tests__/slotRegistry.test.ts
  • src/renderer/src/lib/__tests__/time.test.ts
  • src/renderer/src/lib/__tests__/ui-id.test.ts
  • src/renderer/src/lib/__tests__/utils.test.ts
  • src/renderer/src/lib/artifact-labels.ts
  • src/renderer/src/lib/chat-history.ts
  • src/renderer/src/lib/chat-labels.ts
  • src/renderer/src/lib/chat-queue.test.ts
  • src/renderer/src/lib/chat-queue.ts
  • src/renderer/src/lib/clipboard-write.ts
  • src/renderer/src/lib/device.ts
  • src/renderer/src/lib/image-intent.ts
  • src/renderer/src/lib/image-params.ts
  • src/renderer/src/lib/message-persistence.ts
  • src/renderer/src/lib/model-kind-labels.ts
  • src/renderer/src/lib/notification-hooks.ts
  • src/renderer/src/lib/persist-toggle.ts
  • src/renderer/src/lib/pro-navigation.ts
  • src/renderer/src/lib/session-id.ts
  • src/renderer/src/lib/time.ts
  • src/renderer/src/lib/ui-id.ts
  • src/renderer/src/lib/utils.ts
  • src/renderer/src/lib/voiceApi.ts
  • src/renderer/src/theme.ts
  • src/renderer/src/types.ts
  • src/renderer/src/useMeetingRecorder.ts
  • src/shared/__tests__/device.test.ts
  • src/shared/__tests__/image-generation-contract.test.ts
  • src/shared/__tests__/llm-defaults.test.ts
  • src/shared/__tests__/ports.test.ts
  • src/shared/device.ts
  • src/shared/image-defaults.ts
  • src/shared/image-generation-contract.ts
  • src/shared/ipc-contracts.ts
  • src/shared/llm-defaults.ts
  • src/shared/ports.ts
  • src/shared/product-identity.ts
  • src/shared/renderer-csp.ts
  • tsconfig.base.json
  • tsconfig.node.json
  • tsconfig.web.json
  • vitest.config.ts
  • vitest.db.config.ts

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/quality-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/renderer/src/components/MemoryChat.tsx Outdated
Comment thread src/main/mime.ts
Comment thread src/renderer/src/components/MemoryChat.tsx Outdated
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Quality hardening: bug fixes, pure-logic refactors, coverage gates, and tooling

🐞 Bug fix ✨ Enhancement 🧪 Tests ⚙️ Configuration changes 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Fix MIME/upload/image-intent bugs and add regression tests for each.
• Extract pure logic from Electron/IO shells to enforce SOLID/DRY and improve testability.
• Enforce honest coverage + import-boundary gates in CI with new quality tooling configs.
Diagram

graph TD
  A["CI workflow"] --> B["Vitest coverage gate"] --> H["Unit & render tests"]
  A --> C["Dependency-cruiser gate"] --> D["Main/Renderer modules"]
  D --> E["Pure logic modules"] --> H
  D --> F["LLMService"] --> G["SSE stream transport"]
  D --> I["Mime resolver"]
  subgraph Legend
    direction LR
    _ci["CI/Tooling"] ~~~ _mod["App module"] ~~~ _pure["Pure logic"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use `mime-types`/`mime` npm package for MIME resolution
  • ➕ Avoid maintaining an extension map by hand
  • ➕ Covers many more extensions out of the box
  • ➖ Heavier dependency surface for an app that only needs a small curated set
  • ➖ May yield different/less predictable results than a deliberately limited map
2. Split into stacked PRs (tooling gates vs behavioral fixes vs refactors)
  • ➕ Easier review and rollback if a gate or refactor has issues
  • ➕ Clearer attribution of failures/regressions to a narrower change set
  • ➖ Higher coordination overhead; gates depend on refactors/tests to pass cleanly
  • ➖ Increases merge conflicts and prolongs time-to-protection
3. Generate boundary rules from tsconfig project refs / eslint import rules
  • ➕ Leverages existing TypeScript project structure
  • ➕ One fewer tool in the pipeline
  • ➖ Harder to express graph-level constraints (orphans/circulars/core↛pro) with equivalent signal-to-noise
  • ➖ dependency-cruiser runs fast and is purpose-built for this gate

Recommendation: The PR’s approach (extract pure logic, centralize shared maps/builders, then hard-gate via coverage + depcruise) is the right sequencing for durable quality gains. Consider a follow-up to expand MIME coverage via a library only if format churn becomes frequent; otherwise keep the curated map to preserve predictability. For risk management, future hardening should be delivered as smaller stacked PRs, but the current changes are internally consistent and covered by targeted regression + terminal-artifact tests.

Files changed (66) +3225 / -738

Enhancement (3) +75 / -15
model-filter.tsAdd checkpoint extension helpers and model filtering utilities +20/-0

Add checkpoint extension helpers and model filtering utilities

• Introduces helpers like hasCheckpointExt/stripCheckpointExt/ensureCheckpointExt and exports a shared isImageModelFile decision surface.

src/main/imagegen/model-filter.ts

message-persistence.tsPersist assistant reasoning via message context blob +48/-0

Persist assistant reasoning via message context blob

• Adds buildAssistantContext/readReasoning helpers to carry reasoning through the persisted context so the "Thinking" block survives reload.

src/renderer/src/lib/message-persistence.ts

ChatList.tsxUse shared time/session parsing and update link accent token +7/-15

Use shared time/session parsing and update link accent token

• Replaces inline timestamp/session parsing with shared helpers and changes markdown link color token from cyan to emerald/green to match brand palette.

src/renderer/src/components/ChatList.tsx

Bug fix (12) +582 / -256
mime.tsCentralize extension→MIME mapping with caller-specific fallback +40/-0

Centralize extension→MIME mapping with caller-specific fallback

• Adds a single source of truth for MIME resolution, fixing prior drift and mislabels (notably webp/gif) by providing a shared mimeForExt helper.

src/main/mime.ts

index.tsUse shared MIME resolver for ogcapture protocol serving +2/-6

Use shared MIME resolver for ogcapture protocol serving

• Removes the local OGCAPTURE_MIME map and derives content-type via mimeForExt for protocol-streamed file responses.

src/main/index.ts

media-server.tsUse shared MIME resolver for HTTP media responses +2/-7

Use shared MIME resolver for HTTP media responses

• Replaces the local dotted-extension MIME map with mimeForExt to keep media server content-types consistent across the app.

src/main/media-server.ts

data-url.tsDelegate image MIME sniffing to shared resolver +6/-5

Delegate image MIME sniffing to shared resolver

• Updates mimeFromExt to use mimeForExt with an image/png fallback, eliminating divergent inline mime guessing.

src/main/model-server/data-url.ts

files-classify.tsExtract pure upload classification + picker allowlist helpers +42/-0

Extract pure upload classification + picker allowlist helpers

• Adds classifyUpload, uploadPickerExtensions, and sanitizeUploadName with shared extension sets so the router and file picker cannot drift.

src/main/files-classify.ts

rag-ipc.tsDerive upload picker extensions from router classification sets +5/-11

Derive upload picker extensions from router classification sets

• Replaces the hardcoded dialog allowlist with uploadPickerExtensions(), fixing missing formats (gif/bmp/heic/opus/aiff/avi) handled by the router.

src/main/rag-ipc.ts

settings-math.tsMerge mode presets safely and centralize llama-server argv building +85/-0

Merge mode presets safely and centralize llama-server argv building

• Adds applyModePreset() to prevent mode presets from clobbering user-pinned fields, and introduces buildLaunchArgs() as the single source for spawn argv construction.

src/main/llm/settings-math.ts

llm.tsPersist pinned launch settings and reuse shared streaming/argv logic +71/-112

Persist pinned launch settings and reuse shared streaming/argv logic

• Fixes KV-cache preset clobbering by persisting a userExplicit pin-set and merging presets, centralizes spawn args via buildLaunchArgs, and routes streaming through streamCompletion.

src/main/llm.ts

tools.tsUniform tool dispatch + structured tool results + correct attachment MIME +84/-81

Uniform tool dispatch + structured tool results + correct attachment MIME

• Refactors tool execution to a uniform runTool path returning structured ToolResult (text + optional sources/imageRequest), prevents self-citation in search_memory, and fixes image attachment MIME labeling via mimeFromExt.

src/main/tools.ts

image-intent.tsMake renderer image auto-route decision explicit and suppress in agentic mode +31/-0

Make renderer image auto-route decision explicit and suppress in agentic mode

• Adds shouldAutoRouteImage() so the renderer does not pre-decide image intent when agentic tools/connectors are active, fixing the double-decision bug.

src/renderer/src/lib/image-intent.ts

image-params.tsAdd pure image parameter resolution and override storage helpers +90/-0

Add pure image parameter resolution and override storage helpers

• Introduces resolveImageParams/setOverride helpers so per-model steps/size overrides persist and model changes no longer clobber user-typed values.

src/renderer/src/lib/image-params.ts

MemoryChat.tsxFix image intent routing, persist image params, and persist reasoning +124/-34

Fix image intent routing, persist image params, and persist reasoning

• Binds image model selection to the active modal model, persists per-model steps/size overrides and global image settings, suppresses auto-image routing in agentic mode, and persists reasoning into the message context.

src/renderer/src/components/MemoryChat.tsx

Refactor (21) +608 / -352
files.tsReuse extracted upload classification + sanitization helpers +2/-5

Reuse extracted upload classification + sanitization helpers

• Replaces inline extension sets and filename sanitization with imports from files-classify to keep logic centralized and testable.

src/main/files.ts

ipc-query-logic.tsExtract pure IPC query helpers from ipc.ts +80/-0

Extract pure IPC query helpers from ipc.ts

• Moves JSON parsing, tokenization, trivial-message detection, and app-name LIKE clause building into a side-effect-free module for unit testing.

src/main/ipc-query-logic.ts

ipc.tsDelegate query utilities and standardize modality queue job usage +38/-197

Delegate query utilities and standardize modality queue job usage

• Removes duplicated helper implementations in favor of ipc-query-logic imports and uses the shared CHAT_JOB descriptor for queue runs.

src/main/ipc.ts

queue.tsDefine standard CHAT_JOB and IMAGE_JOB descriptors +7/-0

Define standard CHAT_JOB and IMAGE_JOB descriptors

• Adds frozen QueueRequest constants for chat and image work to eliminate repeated inline descriptors and prevent accidental mutation.

src/main/modality-queue/queue.ts

imagegen.tsUse IMAGE_JOB and centralize checkpoint extension handling +6/-6

Use IMAGE_JOB and centralize checkpoint extension handling

• Switches queue usage to IMAGE_JOB and uses shared model-filter helpers to detect/strip/ensure checkpoint extensions for loras and model files.

src/main/imagegen.ts

stream.tsCreate single SSE streaming transport for chat and tool loops +118/-0

Create single SSE streaming transport for chat and tool loops

• Deduplicates the SSE streaming implementation into streamCompletion(), handling reasoning/content routing, tool-call accumulation, timeouts, and cooperative abort.

src/main/llm/stream.ts

tools-parsers.tsExtract pure HTML/text parsing helpers for web tools +37/-0

Extract pure HTML/text parsing helpers for web tools

• Moves entity decoding, tag stripping, HTML-to-text conversion, and DuckDuckGo href unwrapping into a pure module for unit testing.

src/main/tools-parsers.ts

skills-parse.tsExtract pure skill parsing and trigger logic +87/-0

Extract pure skill parsing and trigger logic

• Moves frontmatter parsing, trigger parsing/serialization, and slug generation into a pure module for unit testing and reuse.

src/main/skills-parse.ts

skills.tsUse extracted skill parsers and re-export types +3/-75

Use extracted skill parsers and re-export types

• Converts skills.ts into an IO shell for filesystem CRUD while delegating parsing and trigger logic to skills-parse.

src/main/skills.ts

tts-logic.tsExtract pure TTS selection and NDJSON parsing helpers +37/-0

Extract pure TTS selection and NDJSON parsing helpers

• Adds chooseVoice, parseServeLine, and teardown-noise detection as pure helpers so tts.ts orchestration remains testable without spawning workers.

src/main/tts-logic.ts

tts.tsDelegate TTS parsing/selection to tts-logic +5/-12

Delegate TTS parsing/selection to tts-logic

• Replaces inline voice selection, teardown-noise checks, and JSON parsing of worker lines with calls into tts-logic.

src/main/tts.ts

vectors-predicates.tsExtract LanceDB delete predicate builders +16/-0

Extract LanceDB delete predicate builders

• Adds pure helpers to build escaped kind predicates and cutoff filters, keeping vectors.ts IO thin and unit-testable.

src/main/vectors-predicates.ts

vectors.tsUse extracted predicate builders for vector deletes +3/-4

Use extracted predicate builders for vector deletes

• Replaces inline string escaping with calls to kindsPredicate/olderThanPredicate for safer, testable query construction.

src/main/vectors.ts

prompt.tsExtract pure project-chat prompt assembly +38/-0

Extract pure project-chat prompt assembly

• Adds buildProjectPrompt and formatHistory helpers so prompt formatting is testable and shared rather than embedded in rag/index.ts.

src/main/rag/prompt.ts

index.tsUse shared project prompt builders +7/-12

Use shared project prompt builders

• Replaces inline history formatting and prompt assembly with buildProjectPrompt/formatHistory imports to prevent drift and improve testability.

src/main/rag/index.ts

search-ranking.tsCentralize LIKE column sets and epoch-ms conversion helpers +39/-0

Centralize LIKE column sets and epoch-ms conversion helpers

• Introduces epochMsSql(), LIKE_COLUMNS, and likeMatch() to keep facet counts and result queries consistent and eliminate duplicated SQL fragments.

src/main/search-ranking.ts

search.tsUse shared likeMatch/LIKE_COLUMNS and epochMsSql for search queries +22/-23

Use shared likeMatch/LIKE_COLUMNS and epochMsSql for search queries

• Refactors facets and LIKE-based search paths to use centralized query builders, preventing count/result drift and reducing duplicated SQL.

src/main/search.ts

ffmpeg-decode.tsCentralize ffmpeg decode args and timeout for transcription +19/-0

Centralize ffmpeg decode args and timeout for transcription

• Adds decodeToWavArgs() and DECODE_TIMEOUT_MS as the shared contract for decoding audio/video inputs to 16kHz mono WAV across whisper/parakeet paths.

src/main/transcription/ffmpeg-decode.ts

session-id.tsCentralize parsing of session IDs for title/model labels +25/-0

Centralize parsing of session IDs for title/model labels

• Adds parseSessionId() as a shared helper used by ChatList and ChatDetail to avoid drift in session label formatting.

src/renderer/src/lib/session-id.ts

time.tsAdd shared SQLite UTC timestamp parsing helper +12/-1

Add shared SQLite UTC timestamp parsing helper

• Introduces parseSqliteUtc() and uses it as the single source for parsing SQLite timestamps stored without timezone information.

src/renderer/src/lib/time.ts

ChatDetail.tsxUse shared time and session parsing helpers +7/-17

Use shared time and session parsing helpers

• Replaces duplicated SQLite UTC parsing and session ID formatting with parseSqliteUtc and parseSessionId for consistent display behavior.

src/renderer/src/components/ChatDetail.tsx

Tests (17) +1411 / -1
mime.test.tsAdd regression tests for mimeForExt and webp/tooling contract +59/-0

Add regression tests for mimeForExt and webp/tooling contract

• Tests MIME resolution behavior (dots/case/fallbacks) and guards against reintroducing the old png/jpeg ternary in tools.ts via source-contract assertions.

src/main/tests/mime.test.ts

files-classify.test.tsAdd tests for upload classification, picker list, and sanitization +123/-0

Add tests for upload classification, picker list, and sanitization

• Covers classifyUpload routing, ensures picker extensions include all classified types without duplicates, and validates sanitizeUploadName behavior.

src/main/tests/files-classify.test.ts

model-filter.test.tsAdd tests for image model filtering/extension helpers +24/-1

Add tests for image model filtering/extension helpers

• Adds unit tests covering the new model-filter helpers and expected extension handling behavior.

src/main/imagegen/tests/model-filter.test.ts

kv-launch-roundtrip.test.tsAdd regression test for KV-cache persistence across mode pick + restart +127/-0

Add regression test for KV-cache persistence across mode pick + restart

• Exercises the settings→persist→reload→launch-args pipeline to ensure explicit kvCacheType survives mode preset application and restart.

src/main/llm/tests/kv-launch-roundtrip.test.ts

settings-merge.test.tsTest mode preset merge behavior vs user-pinned fields +99/-0

Test mode preset merge behavior vs user-pinned fields

• Validates applyModePreset merge semantics so presets fill only unpinned fields.

src/main/llm/tests/settings-merge.test.ts

stream.test.tsAdd tests for streaming completion behavior +121/-0

Add tests for streaming completion behavior

• Adds unit coverage around the streaming path to ensure consistent reasoning/content handling and tool-call assembly.

src/main/llm/tests/stream.test.ts

tools-parsers.test.tsAdd tests for tools HTML parsing helpers +72/-0

Add tests for tools HTML parsing helpers

• Covers decodeEntities/stripTags/htmlToText/decodeDdgHref behavior without requiring network/tool loop execution.

src/main/tests/tools-parsers.test.ts

tools-stream.test.tsAdd regression tests for tool streaming/agent loop behavior +53/-0

Add regression tests for tool streaming/agent loop behavior

• Exercises the tool loop’s streaming/tool-call handling to ensure refactors preserve expected orchestration and outputs.

src/main/tests/tools-stream.test.ts

skills-parse.test.tsAdd unit tests for skills parsing/slug/trigger behavior +124/-0

Add unit tests for skills parsing/slug/trigger behavior

• Covers frontmatter extraction, trigger parsing defaults, serialization, and slug constraints.

src/main/tests/skills-parse.test.ts

tts-logic.test.tsAdd tests for TTS helper logic +69/-0

Add tests for TTS helper logic

• Validates voice selection precedence, NDJSON parsing tolerance, and teardown-noise classification.

src/main/tests/tts-logic.test.ts

vectors-predicates.test.tsAdd tests for vector predicate escaping and cutoff flooring +49/-0

Add tests for vector predicate escaping and cutoff flooring

• Covers SQL escaping of single quotes and integer flooring of cutoff epoch milliseconds.

src/main/tests/vectors-predicates.test.ts

rag-prompt.test.tsAdd tests for project prompt assembly +76/-0

Add tests for project prompt assembly

• Validates prompt structure and history formatting behavior for project chat.

src/main/rag/tests/rag-prompt.test.ts

search-ranking.test.tsAdd tests for search ranking/query helper contracts +50/-0

Add tests for search ranking/query helper contracts

• Adds regression coverage ensuring the shared builders produce consistent clauses/args and stable ranking behavior.

src/main/tests/search-ranking.test.ts

ffmpeg-decode.test.tsAdd tests for shared ffmpeg decode contract +42/-0

Add tests for shared ffmpeg decode contract

• Asserts exact argv shape and key invariants (16kHz, mono, wav, -vn, input/output placement) plus decode timeout.

src/main/transcription/tests/ffmpeg-decode.test.ts

MemoryChat.image.test.tsxAdd terminal-artifact render tests for image composer wiring and intent routing +243/-0

Add terminal-artifact render tests for image composer wiring and intent routing

• Mounts MemoryChat in jsdom and asserts the actual IPC payloads for generateImage/setActiveModalModel and that image intent is not pre-decided in agentic mode.

src/renderer/src/components/tests/MemoryChat.image.test.tsx

chatlist-brand.test.tsAdd contract test guarding ChatList brand link color token +21/-0

Add contract test guarding ChatList brand link color token

• Reads ChatList source to assert cyan tokens are absent and the emerald/green link class is used.

src/renderer/src/components/tests/chatlist-brand.test.ts

message-persistence.test.tsAdd tests for reasoning context carrier functions +59/-0

Add tests for reasoning context carrier functions

• Covers reasoning round-trip, blank/noise suppression, preservation of existing context fields, immutability, and malformed inputs.

src/renderer/src/lib/tests/message-persistence.test.ts

Documentation (4) +165 / -90
CLAUDE.mdDocument quality/coverage/tooling expectations +18/-0

Document quality/coverage/tooling expectations

• Adds/updates project guidance around the new quality-hardening gates and review/testing expectations.

CLAUDE.md

README.mdAdd coverage badges and notes about enforced floors +12/-0

Add coverage badges and notes about enforced floors

• Adds shields for coverage metrics and documents that coverage thresholds are enforced via test:coverage in CI and pre-push.

README.md

CODE_AUDIT.mdAdd/refresh remaining audit backlog list +57/-0

Add/refresh remaining audit backlog list

• Introduces a curated backlog of deferred refactors and verification gaps tracked outside this PR.

docs/CODE_AUDIT.md

GAPS_BACKLOG.mdRefresh gaps backlog content +78/-90

Refresh gaps backlog content

• Reworks the gaps/backlog document to reflect the current state after the hardening pass.

docs/GAPS_BACKLOG.md

Other (9) +384 / -24
.dependency-cruiser.cjsAdd dependency-cruiser import-boundary ruleset +109/-0

Add dependency-cruiser import-boundary ruleset

• Introduces a blocking dependency-cruiser configuration enforcing open-core boundaries (core ↛ pro), purity constraints for extracted logic modules, and hygiene checks (unresolvable, circular, phantom deps).

.dependency-cruiser.cjs

ci.ymlEnforce coverage + dependency boundaries in CI +15/-2

Enforce coverage + dependency boundaries in CI

• Switches CI test step to run coverage with enforced thresholds and adds a dependency-cruiser boundary gate as a fast blocking job.

.github/workflows/ci.yml

eslint.config.mjsAdd typed dead-branch warnings and sonarjs pro lint +60/-0

Add typed dead-branch warnings and sonarjs pro lint

• Adds typed @typescript-eslint/no-unnecessary-condition (warn ratchet) and sonarjs recommended rules scoped to pro/** (warn ratchet), with non-signal rules disabled.

eslint.config.mjs

knip.jsonAdd knip configuration for dead-code detection +18/-0

Add knip configuration for dead-code detection

• Defines knip entrypoints and project globs across core/pro/renderer/e2e/scripts, with ignores for d.ts and specific asset dirs.

knip.json

package.jsonAdd quality tooling scripts and dev dependencies +11/-1

Add quality tooling scripts and dev dependencies

• Adds depcruise/knip scripts and introduces dependencies for dependency-cruiser, knip, jsdom, testing-library, sonarjs lint, and typed ESLint support.

package.json

sonar-project.propertiesAdd SonarCloud project configuration +16/-0

Add SonarCloud project configuration

• Adds SonarCloud project metadata/properties to enable automatic analysis of the core repo.

sonar-project.properties

tsconfig.node.jsonTighten TypeScript dead-code gates (node) +5/-0

Tighten TypeScript dead-code gates (node)

• Enables stricter compiler options to prevent unused locals/params and unreachable code in the node/main TS config.

tsconfig.node.json

tsconfig.web.jsonTighten TypeScript dead-code gates (web) +4/-0

Tighten TypeScript dead-code gates (web)

• Enables stricter compiler options to prevent unused locals/params and unreachable code in the renderer TS config.

tsconfig.web.json

vitest.config.tsMake coverage honest, add render-test harness, and enforce thresholds +146/-21

Make coverage honest, add render-test harness, and enforce thresholds

• Switches coverage to all:true with explicit include/exclude to measure the true unit-testable .ts surface, adds renderer path aliases and .test.tsx support, and raises ratcheted thresholds (including optional pro/** group).

vitest.config.ts

@qodo-code-review

qodo-code-review Bot commented Jul 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. RAG images mislabeled JPEG ✓ Resolved 🐞 Bug ≡ Correctness
Description
The RAG chat image-attachment pipeline currently mislabels non-PNG uploads by forcing them to
image/jpeg, and the shared extension→MIME resolver also omits newly-allowed image types (e.g.,
bmp/heic), leading to data URLs or served responses whose declared MIME doesn’t match the underlying
bytes and can break rendering or vision/model ingestion.
Code

src/main/rag-ipc.ts[R25-28]

+// Built from the router's classify sets (files-classify) so the picker allowlist
+// and the processor can never drift: it used to hardcode a subset that omitted
+// gif/bmp/heic/opus/aiff/avi the router actually handles.
+const DOC_FILTERS = [{ name: 'Documents, audio & video', extensions: uploadPickerExtensions() }];
Relevance

⭐⭐⭐ High

Team previously fixed chat image handling and added data-url tests; PRs prioritize correct MIME for
attachments.

PR-#6
PR-#40

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The renderer passes attachment imagePaths into the RAG chat IPC, after which the main process
reads those files and base64-encodes them in LLMService.decodeImages() using an imageMime()
helper that applies a binary rule (.pngimage/png, everything else → image/jpeg), so formats
like webp/gif (and now bmp/heic) are mislabeled in generated data URLs. Separately, the project’s
shared EXT_MIME/mimeForExt() table is the source of truth for MIME resolution in multiple call
sites (including media serving and data-URL generation paths that use fallbacks like
application/octet-stream or image/png), but it lacks mappings for picker/router-allowed
extensions such as bmp and heic, ensuring those files will be emitted with fallback MIME types
that do not match the persisted bytes.

src/renderer/src/components/MemoryChat.tsx[721-728]
src/renderer/src/components/MemoryChat.tsx[944-951]
src/main/llm.ts[203-215]
src/main/llm/chat-payload.ts[17-21]
src/main/llm/tests/chat-payload.test.ts[14-24]
src/main/rag-ipc.ts[25-28]
src/main/mime.ts[11-40]
src/main/files-classify.ts[7-13]
src/main/rag-ipc.ts[25-55]
src/main/tools.ts[344-355]
src/main/media-server.ts[56-70]
src/main/tests/mime.test.ts[38-44]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
RAG chat image attachments are converted to `data:<mime>;base64,...` URLs in `LLMService.decodeImages()`, but the current `imageMime(imgPath)` implementation forces everything except `.png` to `image/jpeg`, which mislabels valid uploads (webp/gif/bmp/heic/etc.) and can break inline rendering or vision/model ingestion. In addition, the shared extension→MIME mapping (`EXT_MIME`/`mimeForExt()`) used across media serving and data-URL generation omits newly-allowed image extensions (notably `bmp` and `heic`), causing fallback MIME types like `application/octet-stream` or `image/png` to be emitted for those files.

## Issue Context
- The renderer collects `imagePaths` from attachments and passes them to `window.api.ragChat(..., imagePaths)`.
- `LLMService.decodeImages()` reads those files and sets the data URL MIME via `imageMime()`.
- The upload picker allowlist is derived from `uploadPickerExtensions()` and now includes additional non-JPEG formats (including `bmp`/`heic`), and uploads are persisted without format conversion.
- `src/main/mime.ts` is intended as the single source of truth for extension→MIME, but missing entries force downstream call sites (media-server/ogcapture responses and tools/image attachment data URLs) to fall back to incorrect declared MIME types.

## Fix Focus Areas
- src/main/llm/chat-payload.ts[17-21]
- src/main/llm.ts[203-215]
- src/main/llm/__tests__/chat-payload.test.ts[14-24]
- src/renderer/src/components/MemoryChat.tsx[721-728]
- src/renderer/src/components/MemoryChat.tsx[944-951]
- src/main/mime.ts[11-29]
- src/main/__tests__/mime.test.ts[38-44]
- src/main/files-classify.ts[7-9]
- src/main/tools.ts[344-355]
- src/main/media-server.ts[56-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/main/rag-ipc.ts Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/main/ipc.ts (1)

170-178: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Notification broadcast unguarded against destroyed webContents.

Every other sender.send/webContents.send call in this file wraps the send in a try/catch (/* window gone */), but this new notification:new-memory broadcast doesn't. If a window closes between getAllWindows() and send(), this throws inside insertMemoryRecord and propagates to its caller.

🛡️ Guard the broadcast like the rest of the file
     if (memoryId) {
         BrowserWindow.getAllWindows().forEach(win => {
-            win.webContents.send('notification:new-memory', {
-                sessionId: params.sessionId || null,
-                memoryContent: content.slice(0, 100) + (content.length > 100 ? '...' : '')
-            });
+            try {
+                win.webContents.send('notification:new-memory', {
+                    sessionId: params.sessionId || null,
+                    memoryContent: content.slice(0, 100) + (content.length > 100 ? '...' : '')
+                });
+            } catch { /* window gone */ }
         });
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ipc.ts` around lines 170 - 178, Guard the notification:new-memory
broadcast in insertMemoryRecord with a per-window try/catch around
win.webContents.send, matching the existing “window gone” handling used by other
sender.send/webContents.send calls so a destroyed window cannot propagate an
exception.
src/main/files.ts (1)

21-73: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Route via classifyUpload() instead of duplicating its branch logic.

files-classify.ts centralizes the ext→kind mapping in classifyUpload(), but processUpload still re-implements the same if/else chain against the raw extension arrays (Lines 27, 38, 42, 56, 70) instead of calling the extracted function. This is exactly the duplicated "routing rule" the coding guideline for **/*.{ts,tsx} warns against — two independent decision paths for the same mapping can drift.

♻️ Suggested refactor
-import { IMAGE_EXT, AUDIO_EXT, VIDEO_EXT, sanitizeUploadName } from './files-classify';
+import { classifyUpload, sanitizeUploadName } from './files-classify';
 
 export async function processUpload(name: string, bytes: ArrayBuffer | Uint8Array): Promise<ProcessedFile> {
-  const ext = path.extname(name).slice(1).toLowerCase();
+  const kind = classifyUpload(name);
   const safe = sanitizeUploadName(name);
   const tmp = path.join(os.tmpdir(), `offgrid-upload-${Date.now()}-${safe}`);
   await fs.promises.writeFile(tmp, Buffer.from(bytes as ArrayBuffer));
   try {
-    if (IMAGE_EXT.includes(ext)) {
+    if (kind === 'image') {
       ...
     }
-    if (AUDIO_EXT.includes(ext)) {
+    if (kind === 'audio') {
       ...
     }
-    if (VIDEO_EXT.includes(ext)) {
+    if (kind === 'video') {
       ...
     }
-    if (ext === 'pdf') {
+    if (kind === 'pdf') {
       ...
     }
-    if (ext === 'docx') return { ... };
+    if (kind === 'docx') return { ... };
     ...
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/files.ts` around lines 21 - 73, Refactor processUpload to call
classifyUpload() for extension-to-kind routing instead of directly checking
IMAGE_EXT, AUDIO_EXT, VIDEO_EXT, and individual extensions. Use the returned
kind to dispatch the existing image, audio, video, PDF, DOCX, and text
processing while preserving their current behavior and cleanup handling; remove
the duplicated branch conditions and imports no longer needed.

Source: Coding guidelines

🧹 Nitpick comments (8)
src/main/skills-parse.ts (1)

57-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace literal BOM character in regex with an explicit escape.

The regex embeds a literal BOM () character directly in source, which is invisible in most editors/diffs and trips ESLint's no-irregular-whitespace rule.

🔧 Proposed fix
-    const fm = /^?---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/.exec(md);
+    const fm = /^\uFEFF?---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/.exec(md);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/skills-parse.ts` at line 57, Replace the literal BOM character in
the frontmatter regex used by the skills parser with the explicit Unicode escape
\uFEFF, preserving the optional BOM-matching behavior and avoiding irregular
whitespace lint errors.

Source: Linters/SAST tools

src/main/licensing/keygen-client.ts (1)

64-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider typing the JSON:API response shapes instead of any.

toLicense, parseValidateResult, parseActivateResult, and parseMachines are now exported/reusable parsing helpers but all accept any for body/data, flagged by ESLint (no-explicit-any) at Lines 64, 75, 88, 90, 102, 103. A minimal JSON:API shape type would preserve the parsing flexibility while catching response-shape drift at compile time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/licensing/keygen-client.ts` around lines 64 - 112, Replace the
explicit any parameters and local arrays in toLicense, parseValidateResult,
parseActivateResult, and parseMachines with reusable minimal JSON:API response
types, allowing optional attributes, meta, data, and errors fields while
preserving current null-safe parsing behavior. Type nested license, machine,
validation metadata, and error shapes sufficiently to access the existing
properties without disabling no-explicit-any.

Source: Linters/SAST tools

src/main/modality-queue/queue.ts (1)

42-43: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Shallow freeze leaves evicts array mutable.

Object.freeze only freezes the top-level object; evicts is a separate array reference that can still be mutated in place (push/pop/sort), silently corrupting the shared singleton descriptor for every future run() call. The comment's claim that "a caller can't mutate the shared descriptor" doesn't fully hold.

🔒 Deep-freeze the evicts array
-export const CHAT_JOB: QueueRequest = Object.freeze({ tier: 2, label: 'chat', evicts: ['image'] });
-export const IMAGE_JOB: QueueRequest = Object.freeze({ tier: 2, label: 'image', evicts: ['llm'] });
+export const CHAT_JOB: QueueRequest = Object.freeze({ tier: 2, label: 'chat', evicts: Object.freeze(['image']) });
+export const IMAGE_JOB: QueueRequest = Object.freeze({ tier: 2, label: 'image', evicts: Object.freeze(['llm']) });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/modality-queue/queue.ts` around lines 42 - 43, Deep-freeze the
nested evicts arrays in the shared CHAT_JOB and IMAGE_JOB descriptors by
freezing each array before freezing its containing object, preserving immutable
singleton state for all future run() calls.
src/main/ipc.ts (1)

12-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the dead _newSummary parameter. updateMasterMemoryIncremental never reads it, so either remove it and update the call site or keep it only as a deliberate compatibility placeholder.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ipc.ts` around lines 12 - 16, Remove the unused _newSummary
parameter from updateMasterMemoryIncremental and update every call site to
invoke it without arguments; alternatively, retain it only if compatibility
requires it and document that intentional placeholder.

Source: Linters/SAST tools

src/main/tools-parsers.ts (1)

22-28: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

htmlToText misses self-closing <br>/<br/> line breaks.

The block-close regex only matches </br>; real-world HTML almost always uses <br> or <br/>, so those breaks are lost (collapsed into surrounding text) while </p>, </div>, etc. still work.

♻️ Proposed fix to also match self-closing br
-    .replace(/<\/(p|div|li|h[1-6]|br|tr|section|article)>/gi, '\n');
+    .replace(/<\/(p|div|li|h[1-6]|tr|section|article)>/gi, '\n')
+    .replace(/<br\s*\/?>/gi, '\n');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/tools-parsers.ts` around lines 22 - 28, Update htmlToText so its
tag-to-newline replacement recognizes opening and self-closing br elements,
including <br>, <br/>, and whitespace variants, while preserving the existing
closing-tag handling for paragraphs and other block elements.
src/main/files-classify.ts (1)

7-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider freezing the exported extension arrays.

These arrays are now the single source of truth consumed by files.ts, rag-ipc.ts, and tests. As plain mutable arrays, an accidental .push()/.sort() from any importer would silently change classification everywhere they're shared.

🛡️ Optional hardening
-export const IMAGE_EXT = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'heic'];
-export const AUDIO_EXT = ['mp3', 'wav', 'm4a', 'aac', 'ogg', 'oga', 'opus', 'flac', 'aiff', 'aif'];
-export const VIDEO_EXT = ['mp4', 'mov', 'webm', 'mkv', 'avi', 'm4v'];
+export const IMAGE_EXT = ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'heic'] as const;
+export const AUDIO_EXT = ['mp3', 'wav', 'm4a', 'aac', 'ogg', 'oga', 'opus', 'flac', 'aiff', 'aif'] as const;
+export const VIDEO_EXT = ['mp4', 'mov', 'webm', 'mkv', 'avi', 'm4v'] as const;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/files-classify.ts` around lines 7 - 13, Freeze the exported
extension arrays IMAGE_EXT, AUDIO_EXT, VIDEO_EXT, and DOC_EXT to prevent
importers from mutating shared classification data. Apply Object.freeze (with
suitable readonly typing if needed) while preserving their existing values and
usages in files.ts, rag-ipc.ts, and tests.
src/main/__tests__/prompts.test.ts (1)

17-20: 📐 Maintainability & Code Quality | 🔵 Trivial

Consider a real temp-DB collaborator instead of mocking ../database wholesale.

getSetting/deleteSetting are stubbed out entirely rather than exercised against a real (temporary) SQLite instance, even though the module reads/writes via a DB that can run in-process. This is a defensible boundary call given the file's scope is the pure template engine, but it diverges from the general test-writing preference.

As per coding guidelines, "Prefer integration tests with real collaborators such as temporary SQLite databases, temporary user-data directories, real cryptography, or real WASM; mock only boundaries that cannot run in-process."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/__tests__/prompts.test.ts` around lines 17 - 20, Replace the
wholesale ../database mock in prompts.test.ts with a real temporary SQLite
database collaborator, configuring test setup and teardown to create and clean
up an isolated database. Exercise getSetting and deleteSetting against that
database while preserving the template-engine test behavior, and mock only
dependencies that cannot run in-process.

Source: Coding guidelines

src/main/bootstrap/__tests__/proEnabled.test.ts (1)

54-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the unnecessary type cast for vi.stubEnv.

Vitest's stubEnv signature natively accepts string | undefined for non-mode env vars, so undefined as unknown as string is unneeded (and misleading — it lies about the runtime type). Passing undefined directly is correct.

🧹 Proposed simplification
-    vi.stubEnv('OFFGRID_PRO', undefined as unknown as string);
+    vi.stubEnv('OFFGRID_PRO', undefined);

Also applies to: 61-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/bootstrap/__tests__/proEnabled.test.ts` at line 54, Remove the
unnecessary `as unknown as string` cast from the `vi.stubEnv` calls in the
`proEnabled` tests, including both occurrences, and pass `undefined` directly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/GAPS_BACKLOG.md`:
- Around line 8-12: Replace every em dash in docs/GAPS_BACKLOG.md with approved
punctuation such as a comma, colon, semicolon, or parentheses, preserving the
original meaning across the OPEN and RESOLVED sections and all referenced
passages. Search the entire document to ensure no em dash characters remain.

In `@src/main/licensing/__tests__/device-fingerprint.test.ts`:
- Around line 16-18: Address the shared TypeScript lint violations in
device-fingerprint.test.ts: explicitly annotate freshModule and setPlatform
return types, and handle the intentionally unused _key parameter in the electron
mock via a test-file lint override or an appropriate unused-parameter
suppression.

In `@src/main/llm/__tests__/kv-launch-roundtrip.test.ts`:
- Around line 64-74: Add explicit return types to the `freshService` and
`applySettings` helper functions to satisfy
`@typescript-eslint/explicit-function-return-type`; use the inferred promise
types, with `freshService` returning `Promise<LLMService>` and `applySettings`
returning `Promise<void>`.

In `@src/main/llm/stream.ts`:
- Around line 40-118: Remove the abort event listener registered in the stream
request by tracking the handler and centralizing cleanup in the terminal paths.
Update the logic around opts.signal, the timeout callback, non-200 response
handling, response end, request error, and abort callback so cleanup is
performed exactly once before resolving or rejecting, preventing reused signals
from accumulating listeners.

In `@src/main/mcp-parse-data-url.ts`:
- Around line 14-23: Update parseDataUrl() to validate that url.indexOf(',')
returns a valid delimiter before extracting metadata or decoding the payload;
when comma === -1, reject the malformed data URL explicitly. Preserve decoding
behavior for valid URLs while preventing truncated inputs from reaching
decodeURIComponent().

In `@src/main/models/gguf.ts`:
- Around line 40-52: Ensure the file descriptor opened in isValidGgufFile is
always closed, including when readSync throws. Wrap the readSync and header
validation logic in a try/finally block and call closeSync(fd) in the finally
clause, while preserving the existing false return for caught errors.

In `@src/renderer/src/components/ChatDetail.tsx`:
- Line 62: Add an explicit string return type to both inline formatTime
functions in MemoryCardFull and EntityCardFull, updating their signatures to
satisfy the explicit-function-return-type ESLint rule.

In `@src/renderer/src/components/ChatList.tsx`:
- Line 68: Replace the text-green-500 class on the link in ChatList with the
mandated emerald accent styling, using the appropriate dark/light emerald values
(`#34D399` and `#059669`) via the project’s existing theme or Tailwind classes.
- Around line 316-320: Update formatTime to avoid fixed-position slicing of
toLocaleString(), which is locale-dependent. Using the parsed date from
parseSqliteUtc, format the date and time with explicit Intl.DateTimeFormat
options or a dedicated locale-aware formatter, preserving the intended displayed
components across locales.

In `@src/renderer/src/components/MemoryChat.tsx`:
- Around line 811-816: Reuse the existing agenticActive capability check
throughout MemoryChat’s turn-routing logic. Replace the later inline `(toolsOn
|| connectorsOn) && !activeProjectId` condition guarding the tools/connectors
branch with agenticActive, keeping the variable as the single source of truth
for both shouldAutoRouteImage and the agentic path.
- Around line 551-554: Update chooseImageModel to handle rejected promises from
window.api.setActiveModalModel, matching the error-handling pattern used by
other fire-and-forget IPC calls in MemoryChat.tsx (for example, attach a catch
handler). Preserve the optimistic setImgModel update while ensuring failures are
not silently unhandled.
- Around line 911-920: Thread the captured streamed reasoning through every
cancellation/partial-answer persistence path. In the cancel branches that call
addRagMessage with toolCtx or result.context, build or augment the persisted
context using the captured toolReasoning, matching the finalized path’s
buildAssistantContext(toolCtx, { reasoning: toolReasoning }) behavior so stopped
turns retain their reasoning block.

---

Outside diff comments:
In `@src/main/files.ts`:
- Around line 21-73: Refactor processUpload to call classifyUpload() for
extension-to-kind routing instead of directly checking IMAGE_EXT, AUDIO_EXT,
VIDEO_EXT, and individual extensions. Use the returned kind to dispatch the
existing image, audio, video, PDF, DOCX, and text processing while preserving
their current behavior and cleanup handling; remove the duplicated branch
conditions and imports no longer needed.

In `@src/main/ipc.ts`:
- Around line 170-178: Guard the notification:new-memory broadcast in
insertMemoryRecord with a per-window try/catch around win.webContents.send,
matching the existing “window gone” handling used by other
sender.send/webContents.send calls so a destroyed window cannot propagate an
exception.

---

Nitpick comments:
In `@src/main/__tests__/prompts.test.ts`:
- Around line 17-20: Replace the wholesale ../database mock in prompts.test.ts
with a real temporary SQLite database collaborator, configuring test setup and
teardown to create and clean up an isolated database. Exercise getSetting and
deleteSetting against that database while preserving the template-engine test
behavior, and mock only dependencies that cannot run in-process.

In `@src/main/bootstrap/__tests__/proEnabled.test.ts`:
- Line 54: Remove the unnecessary `as unknown as string` cast from the
`vi.stubEnv` calls in the `proEnabled` tests, including both occurrences, and
pass `undefined` directly.

In `@src/main/files-classify.ts`:
- Around line 7-13: Freeze the exported extension arrays IMAGE_EXT, AUDIO_EXT,
VIDEO_EXT, and DOC_EXT to prevent importers from mutating shared classification
data. Apply Object.freeze (with suitable readonly typing if needed) while
preserving their existing values and usages in files.ts, rag-ipc.ts, and tests.

In `@src/main/ipc.ts`:
- Around line 12-16: Remove the unused _newSummary parameter from
updateMasterMemoryIncremental and update every call site to invoke it without
arguments; alternatively, retain it only if compatibility requires it and
document that intentional placeholder.

In `@src/main/licensing/keygen-client.ts`:
- Around line 64-112: Replace the explicit any parameters and local arrays in
toLicense, parseValidateResult, parseActivateResult, and parseMachines with
reusable minimal JSON:API response types, allowing optional attributes, meta,
data, and errors fields while preserving current null-safe parsing behavior.
Type nested license, machine, validation metadata, and error shapes sufficiently
to access the existing properties without disabling no-explicit-any.

In `@src/main/modality-queue/queue.ts`:
- Around line 42-43: Deep-freeze the nested evicts arrays in the shared CHAT_JOB
and IMAGE_JOB descriptors by freezing each array before freezing its containing
object, preserving immutable singleton state for all future run() calls.

In `@src/main/skills-parse.ts`:
- Line 57: Replace the literal BOM character in the frontmatter regex used by
the skills parser with the explicit Unicode escape \uFEFF, preserving the
optional BOM-matching behavior and avoiding irregular whitespace lint errors.

In `@src/main/tools-parsers.ts`:
- Around line 22-28: Update htmlToText so its tag-to-newline replacement
recognizes opening and self-closing br elements, including <br>, <br/>, and
whitespace variants, while preserving the existing closing-tag handling for
paragraphs and other block elements.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d9dbb58c-155f-4d0a-8f8d-ef63e2a1af57

📥 Commits

Reviewing files that changed from the base of the PR and between 6dfb472 and e54bce7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (138)
  • .dependency-cruiser.cjs
  • .github/workflows/ci.yml
  • CLAUDE.md
  • README.md
  • docs/CODE_AUDIT.md
  • docs/GAPS_BACKLOG.md
  • eslint.config.mjs
  • knip.json
  • package.json
  • sonar-project.properties
  • src/components/ui/focus-cards.tsx
  • src/hooks/use-outside-click.tsx
  • src/main/__tests__/files-classify.test.ts
  • src/main/__tests__/ipc-query-logic.test.ts
  • src/main/__tests__/mcp-parse-data-url.test.ts
  • src/main/__tests__/mime.test.ts
  • src/main/__tests__/prompts.test.ts
  • src/main/__tests__/reasoning-persistence.dbtest.ts
  • src/main/__tests__/search-ranking.test.ts
  • src/main/__tests__/skills-parse.test.ts
  • src/main/__tests__/tools-parsers.test.ts
  • src/main/__tests__/tools-stream.test.ts
  • src/main/__tests__/tts-logic.test.ts
  • src/main/__tests__/vectors-predicates.test.ts
  • src/main/bootstrap/__tests__/hookRegistry.test.ts
  • src/main/bootstrap/__tests__/proEnabled.test.ts
  • src/main/files-classify.ts
  • src/main/files.ts
  • src/main/imagegen.ts
  • src/main/imagegen/__tests__/model-filter.test.ts
  • src/main/imagegen/model-filter.ts
  • src/main/index.ts
  • src/main/ipc-query-logic.ts
  • src/main/ipc.ts
  • src/main/licensing/__tests__/device-fingerprint.test.ts
  • src/main/licensing/__tests__/keygen-parse.test.ts
  • src/main/licensing/__tests__/license-logic.test.ts
  • src/main/licensing/keygen-client.ts
  • src/main/licensing/license-service.ts
  • src/main/llm.ts
  • src/main/llm/__tests__/kv-launch-roundtrip.test.ts
  • src/main/llm/__tests__/settings-merge.test.ts
  • src/main/llm/__tests__/stream.test.ts
  • src/main/llm/settings-math.ts
  • src/main/llm/stream.ts
  • src/main/mcp-parse-data-url.ts
  • src/main/mcp-server.ts
  • src/main/media-server.ts
  • src/main/mime.ts
  • src/main/modality-queue/__tests__/jobs.test.ts
  • src/main/modality-queue/queue.ts
  • src/main/model-server/__tests__/data-url.test.ts
  • src/main/model-server/data-url.ts
  • src/main/models-manager.ts
  • src/main/models/__tests__/gguf.test.ts
  • src/main/models/gguf.ts
  • src/main/rag-ipc.ts
  • src/main/rag/__tests__/rag-prompt.test.ts
  • src/main/rag/index.ts
  • src/main/rag/prompt.ts
  • src/main/search-ranking.ts
  • src/main/search.ts
  • src/main/skills-parse.ts
  • src/main/skills.ts
  • src/main/tools-parsers.ts
  • src/main/tools.ts
  • src/main/tools/__tests__/mcpConnectorToolExtension.test.ts
  • src/main/tools/mcpConnectorToolExtension.ts
  • src/main/transcription/__tests__/ffmpeg-decode.test.ts
  • src/main/transcription/ffmpeg-decode.ts
  • src/main/transcription/parakeet-cli.ts
  • src/main/transcription/whisper-cli.ts
  • src/main/transcription/whisper-server.ts
  • src/main/tts-logic.ts
  • src/main/tts.ts
  • src/main/vectors-predicates.ts
  • src/main/vectors.ts
  • src/preload/index.ts
  • src/renderer/src/__tests__/theme.test.ts
  • src/renderer/src/components/ArtifactCanvas.tsx
  • src/renderer/src/components/ChatDetail.tsx
  • src/renderer/src/components/ChatList.tsx
  • src/renderer/src/components/ConnectorsScreen.tsx
  • src/renderer/src/components/MemoryChat.tsx
  • src/renderer/src/components/ModelsScreen.tsx
  • src/renderer/src/components/ProjectsScreen.tsx
  • src/renderer/src/components/Settings.tsx
  • src/renderer/src/components/__tests__/MemoryChat.image.test.tsx
  • src/renderer/src/components/__tests__/chatlist-brand.test.ts
  • src/renderer/src/components/connectorCatalog.ts
  • src/renderer/src/components/setup/StoragePanel.tsx
  • src/renderer/src/components/ui/accordion.tsx
  • src/renderer/src/components/ui/background-beams-with-collision.tsx
  • src/renderer/src/components/ui/background-gradient-animation.tsx
  • src/renderer/src/components/ui/badge.tsx
  • src/renderer/src/components/ui/bento-grid.tsx
  • src/renderer/src/components/ui/canvas-reveal-effect.tsx
  • src/renderer/src/components/ui/card-spotlight.tsx
  • src/renderer/src/components/ui/colourful-text.tsx
  • src/renderer/src/components/ui/comet-card.tsx
  • src/renderer/src/components/ui/flip-words.tsx
  • src/renderer/src/components/ui/focus-cards.tsx
  • src/renderer/src/components/ui/glare-card.tsx
  • src/renderer/src/components/ui/glowing-effect.tsx
  • src/renderer/src/components/ui/glowing-stars.tsx
  • src/renderer/src/components/ui/hero-highlight.tsx
  • src/renderer/src/components/ui/hover-border-gradient.tsx
  • src/renderer/src/components/ui/scroll-area.tsx
  • src/renderer/src/components/ui/select.tsx
  • src/renderer/src/components/ui/separator.tsx
  • src/renderer/src/components/ui/sheet.tsx
  • src/renderer/src/components/ui/shooting-stars.tsx
  • src/renderer/src/components/ui/sparkles.tsx
  • src/renderer/src/components/ui/spotlight.tsx
  • src/renderer/src/components/ui/stars-background.tsx
  • src/renderer/src/components/ui/tabs.tsx
  • src/renderer/src/components/ui/text-generate-effect.tsx
  • src/renderer/src/components/ui/textarea.tsx
  • src/renderer/src/components/ui/wobble-card.tsx
  • src/renderer/src/lib/__tests__/artifact-labels.test.ts
  • src/renderer/src/lib/__tests__/connectorCatalog.test.ts
  • src/renderer/src/lib/__tests__/image-intent.test.ts
  • src/renderer/src/lib/__tests__/image-params-wiring.test.ts
  • src/renderer/src/lib/__tests__/image-params.test.ts
  • src/renderer/src/lib/__tests__/message-persistence.test.ts
  • src/renderer/src/lib/__tests__/model-kind-labels.test.ts
  • src/renderer/src/lib/__tests__/session-id.test.ts
  • src/renderer/src/lib/__tests__/time.test.ts
  • src/renderer/src/lib/artifact-labels.ts
  • src/renderer/src/lib/image-intent.ts
  • src/renderer/src/lib/image-params.ts
  • src/renderer/src/lib/message-persistence.ts
  • src/renderer/src/lib/model-kind-labels.ts
  • src/renderer/src/lib/session-id.ts
  • src/renderer/src/lib/time.ts
  • tsconfig.node.json
  • tsconfig.web.json
  • vitest.config.ts
💤 Files with no reviewable changes (30)
  • src/hooks/use-outside-click.tsx
  • src/renderer/src/components/ui/wobble-card.tsx
  • src/renderer/src/components/ui/scroll-area.tsx
  • src/renderer/src/components/ui/accordion.tsx
  • src/renderer/src/components/ui/comet-card.tsx
  • src/renderer/src/components/ui/colourful-text.tsx
  • src/renderer/src/components/ui/textarea.tsx
  • src/renderer/src/components/ui/bento-grid.tsx
  • src/renderer/src/components/ui/glare-card.tsx
  • src/renderer/src/components/ui/hover-border-gradient.tsx
  • src/renderer/src/components/ui/separator.tsx
  • src/components/ui/focus-cards.tsx
  • src/renderer/src/components/ui/stars-background.tsx
  • src/renderer/src/components/ui/flip-words.tsx
  • src/renderer/src/components/ui/text-generate-effect.tsx
  • src/renderer/src/components/ui/sheet.tsx
  • src/renderer/src/components/ui/shooting-stars.tsx
  • src/renderer/src/components/ui/sparkles.tsx
  • src/renderer/src/components/ui/background-gradient-animation.tsx
  • src/renderer/src/components/ui/badge.tsx
  • src/renderer/src/components/ui/focus-cards.tsx
  • src/renderer/src/components/ui/background-beams-with-collision.tsx
  • src/renderer/src/components/ui/spotlight.tsx
  • src/renderer/src/components/ui/tabs.tsx
  • src/renderer/src/components/ui/hero-highlight.tsx
  • src/renderer/src/components/ui/card-spotlight.tsx
  • src/renderer/src/components/ui/glowing-stars.tsx
  • src/renderer/src/components/ui/glowing-effect.tsx
  • src/renderer/src/components/ui/canvas-reveal-effect.tsx
  • src/renderer/src/components/ui/select.tsx

Comment thread docs/GAPS_BACKLOG.md
Comment thread src/main/licensing/__tests__/device-fingerprint.test.ts Outdated
Comment thread src/main/llm/__tests__/kv-launch-roundtrip.test.ts Outdated
Comment thread src/main/llm/stream.ts
Comment thread src/main/mcp-parse-data-url.ts
Comment thread src/renderer/src/components/ChatList.tsx Outdated
Comment thread src/renderer/src/components/ChatList.tsx Outdated
Comment thread src/renderer/src/components/MemoryChat.tsx Outdated
Comment thread src/renderer/src/components/MemoryChat.tsx Outdated
Comment thread src/renderer/src/components/MemoryChat.tsx Outdated

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

37-41: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pin the fallback checkout to main. The fallback step omits ref, so it tracks the repository’s default branch instead of the intended main. Add ref: main to keep CI behavior stable.

🔧 Proposed fix
         with:
           repository: off-grid-ai/desktop-pro
           token: ${{ secrets.CI_CROSS_REPO_TOKEN }}
           path: pro
+          ref: main
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 37 - 41, Update the fallback checkout
step using actions/checkout@v4 to include ref: main alongside repository, token,
and path, ensuring it always checks out the intended branch rather than the
repository default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 34-37: Remove continue-on-error from the fallback
actions/checkout@v4 step named “Fall back to pro main if no matching branch,” or
explicitly fail when the pro checkout is unavailable, so failures in the pro
typecheck/coverage path cannot be silently skipped.
- Around line 25-33: Add persist-credentials: false to both actions/checkout
steps for desktop-pro, including the checkout identified by id pro_branch, so
the CI token is not retained in local git configuration.

---

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 37-41: Update the fallback checkout step using actions/checkout@v4
to include ref: main alongside repository, token, and path, ensuring it always
checks out the intended branch rather than the repository default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e6c962d-ded6-461e-8ce3-9f18794fe010

📥 Commits

Reviewing files that changed from the base of the PR and between e54bce7 and 786a721.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
alichherawalla pushed a commit that referenced this pull request Jul 10, 2026
…ss + FD leak

From the PR #46 bot reviews, the confirmed real bugs (several in this PR's own
mislabel/drift class):

- MIME map omitted bmp/heic (Gitar + Qodo): files-classify's IMAGE_EXT accepts them,
  so a .bmp/.heic upload was mislabelled via the image/png fallback. Added both; a
  test now asserts EVERY IMAGE_EXT resolves to a real image/* MIME (imported from the
  router — test-side DRY — so the map can't drift from the accept-list again).
- imageMime (llm/chat-payload, the RAG-chat vision path via decodeImages) still did
  the old `endsWith('.png') ? png : jpeg` — the exact webp mislabel I fixed in
  tools.ts but missed here (Qodo). Routed through the shared mimeForExt (image/png
  fallback); webp→image/webp, gif→image/gif.
- gguf.isValidGgufFile FD leak (CodeRabbit, Major): closeSync only ran on success;
  a readSync throw leaked the descriptor. Wrapped in try/finally.
- MemoryChat.chooseImageModel write-through swallowed a rejected setActiveModalModel
  (CodeRabbit): added .catch+log so a failed persist can't silently re-diverge the
  composer from the Active-models panel.

tsc x3 clean; full suite 2113 pass; coverage 97/92/96/98.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 1 resolved / 2 findings

Hardens code quality by unifying MIME maps, purging unused UI templates, and implementing strict dead-code gates. Addresses the HEIC/BMP fallback issue, but updates are needed to move unreliable side effects out of state updater functions.

⚠️ Bug: Reasoning captured via side effect inside setState updater is unreliable

📄 src/renderer/src/components/MemoryChat.tsx:913-920 📄 src/renderer/src/components/MemoryChat.tsx:987-993 📄 src/renderer/src/components/MemoryChat.tsx:1003 📄 src/renderer/src/components/MemoryChat.tsx:196-199 📄 src/renderer/src/components/MemoryChat.tsx:557-571

Both persistence sites capture the streamed reasoning by mutating an outer variable inside the setConvMessages updater and then reading it on the very next line:

let toolReasoning: string | undefined;
setConvMessages(convId, prev => prev.map(m => {
  if (m.id !== toolStreamId) return m;
  toolReasoning = m.reasoning;      // side effect inside updater
  return { ...m, content: answer, ... };
}));
const toolCtxWithReasoning = buildAssistantContext(toolCtx, { reasoning: toolReasoning });

setConvMessages wraps a normal setState(updater) (line 196-199). React does not guarantee the updater runs synchronously during the dispatch call — it only runs the updater eagerly when the fiber's update queue is empty (the bail-out optimization); otherwise it is deferred to the render phase. When any update is still pending on messagesByConv (e.g. a late onRagStream delta, another tab's stream, StrictMode double-invoke), toolReasoning/ragReasoning are read as undefined, so buildAssistantContext drops the reasoning and the persisted context blob has no reasoning key. On reload the 'Thinking' block silently vanishes — the exact behavior this change (T1f) is meant to fix. Because it depends on React's eager-bail timing, the failure is intermittent and not caught by the pure message-persistence unit tests.

Capture the value outside the updater instead of relying on updater side effects — e.g. mirror the accumulated reasoning in a ref (keyed by streamId) updated in the onRagStream handler, and read that ref when persisting.

Track reasoning in a ref so it is read deterministically, not via an updater side effect.
// keep a ref mirror of accumulated reasoning per stream
const reasoningByStream = useRef<Record<string, string>>({});
// in onRagStream: if (data.type === 'reasoning') reasoningByStream.current[data.streamId] = (reasoningByStream.current[data.streamId] || '') + (data.text || '');

// persist path (tool + rag), no side effect in the updater:
const toolReasoning = reasoningByStream.current[toolStreamId];
setConvMessages(convId, prev => prev.map(m => (m.id === toolStreamId
  ? { ...m, content: answer, context, toolCalls, activity: undefined, streaming: false } : m)));
const toolCtxWithReasoning = buildAssistantContext(toolCtx, { reasoning: toolReasoning });
✅ 1 resolved
Edge Case: HEIC/BMP image attachments fall back to image/png in vision data URL

📄 src/main/mime.ts:11-25 📄 src/main/files-classify.ts:7 📄 src/main/tools.ts:349-354
classifyUpload treats bmp and heic as valid image uploads (IMAGE_EXT includes them), but the shared EXT_MIME map in mime.ts has no entry for bmp or heic. In tools.ts, image attachments are labelled via mimeFromExt(...) which falls back to image/png for unknown extensions. So a .heic/.bmp attachment is base64-encoded with an image/png MIME header while carrying HEIC/BMP bytes — the same class of mislabel this PR fixes for .webp (a vision model may reject the mismatched declared type). Consider either adding bmp: 'image/bmp' (and a HEIC entry / conversion) to the map, or excluding formats from the picker that cannot be correctly labelled/decoded.

🤖 Prompt for agents
Code Review: Hardens code quality by unifying MIME maps, purging unused UI templates, and implementing strict dead-code gates. Addresses the HEIC/BMP fallback issue, but updates are needed to move unreliable side effects out of state updater functions.

1. ⚠️ Bug: Reasoning captured via side effect inside setState updater is unreliable
   Files: src/renderer/src/components/MemoryChat.tsx:913-920, src/renderer/src/components/MemoryChat.tsx:987-993, src/renderer/src/components/MemoryChat.tsx:1003, src/renderer/src/components/MemoryChat.tsx:196-199, src/renderer/src/components/MemoryChat.tsx:557-571

   Both persistence sites capture the streamed reasoning by mutating an outer variable *inside* the `setConvMessages` updater and then reading it on the very next line:
   
   ```
   let toolReasoning: string | undefined;
   setConvMessages(convId, prev => prev.map(m => {
     if (m.id !== toolStreamId) return m;
     toolReasoning = m.reasoning;      // side effect inside updater
     return { ...m, content: answer, ... };
   }));
   const toolCtxWithReasoning = buildAssistantContext(toolCtx, { reasoning: toolReasoning });
   ```
   
   `setConvMessages` wraps a normal `setState(updater)` (line 196-199). React does not guarantee the updater runs synchronously during the dispatch call — it only runs the updater eagerly when the fiber's update queue is empty (the bail-out optimization); otherwise it is deferred to the render phase. When any update is still pending on `messagesByConv` (e.g. a late `onRagStream` delta, another tab's stream, StrictMode double-invoke), `toolReasoning`/`ragReasoning` are read as `undefined`, so `buildAssistantContext` drops the reasoning and the persisted `context` blob has no `reasoning` key. On reload the 'Thinking' block silently vanishes — the exact behavior this change (T1f) is meant to fix. Because it depends on React's eager-bail timing, the failure is intermittent and not caught by the pure `message-persistence` unit tests.
   
   Capture the value outside the updater instead of relying on updater side effects — e.g. mirror the accumulated reasoning in a ref (keyed by streamId) updated in the `onRagStream` handler, and read that ref when persisting.

   Fix (Track reasoning in a ref so it is read deterministically, not via an updater side effect.):
   // keep a ref mirror of accumulated reasoning per stream
   const reasoningByStream = useRef<Record<string, string>>({});
   // in onRagStream: if (data.type === 'reasoning') reasoningByStream.current[data.streamId] = (reasoningByStream.current[data.streamId] || '') + (data.text || '');
   
   // persist path (tool + rag), no side effect in the updater:
   const toolReasoning = reasoningByStream.current[toolStreamId];
   setConvMessages(convId, prev => prev.map(m => (m.id === toolStreamId
     ? { ...m, content: answer, context, toolCalls, activity: undefined, streaming: false } : m)));
   const toolCtxWithReasoning = buildAssistantContext(toolCtx, { reasoning: toolReasoning });

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

alichherawalla pushed a commit that referenced this pull request Jul 10, 2026
…aram updaters

More PR #46 review findings (MemoryChat), all in this PR's own drift/correctness class:

- Reasoning captured via a setState-updater SIDE EFFECT (Gitar Bug + CodeRabbit):
  both persist sites read `m.reasoning` out of the setConvMessages updater then used
  it on the next line. React only runs an updater eagerly on a bail-out, else defers
  it to render → the read could be undefined → the persisted Thinking block vanished
  on reload (the exact T1f bug it was meant to fix). Now mirrored into a
  reasoningByStream ref as it streams (written synchronously in onRagStream) and read
  deterministically at persist; entry freed after use so the map can't grow unbounded.
- agenticActive (Major DRY): the `(toolsOn||connectorsOn)&&!activeProjectId` capability
  was computed into agenticActive then recomputed inline at the tools/connectors gate.
  Reuse the variable — one definition.
- saveSetting inside the setImgParamStore updater (Quality): updaters must be pure
  (StrictMode double-invokes → double IPC save). Moved to one effect keyed on the
  store, gated on prefsLoaded.

tsc x3 clean; MemoryChat render test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
src/renderer/src/components/MemoryChat.tsx (1)

463-491: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

New code omits braces on single-line if statements.

The functional logic here (refreshImageModel, the resolveImageParams effect, setStepsOverride/setSizeOverride) is correct, but the new single-line guards at Lines 472, 488, 541, 564, and 573 (e.g. if (!s) return;, if (!imgModel) return;) omit braces, which conflicts with the planned curly: all hygiene rule for this file type.

As per coding guidelines, **/*.{js,ts,tsx}: "The planned ESLint hygiene rules are curly: all... introduce limits with a ratchet and never lower them."

🧹 Example fix
-  const s = await window.api.imageGenStatus?.();
-  if (!s) return;
+  const s = await window.api.imageGenStatus?.();
+  if (!s) { return; }

Also applies to: 533-578

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/components/MemoryChat.tsx` around lines 463 - 491, Wrap
every single-line if guard in the affected logic with braces to satisfy the
planned curly: all rule, including guards in refreshImageModel, the
resolveImageParams effect, and setStepsOverride/setSizeOverride. Update
conditions such as “if (!s) return;” and “if (!imgModel) return;” while
preserving their existing control flow.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/renderer/src/components/MemoryChat.tsx`:
- Around line 463-491: Wrap every single-line if guard in the affected logic
with braces to satisfy the planned curly: all rule, including guards in
refreshImageModel, the resolveImageParams effect, and
setStepsOverride/setSizeOverride. Update conditions such as “if (!s) return;”
and “if (!imgModel) return;” while preserving their existing control flow.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e39bbe29-7860-464a-9a5f-844e8e44106c

📥 Commits

Reviewing files that changed from the base of the PR and between 786a721 and b7f7034.

📒 Files selected for processing (7)
  • src/main/__tests__/mime.test.ts
  • src/main/llm/__tests__/chat-payload.test.ts
  • src/main/llm/chat-payload.ts
  • src/main/mime.ts
  • src/main/model-server/__tests__/data-url.test.ts
  • src/main/models/gguf.ts
  • src/renderer/src/components/MemoryChat.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/mime.ts
  • src/main/tests/mime.test.ts
  • src/main/models/gguf.ts

alichherawalla pushed a commit that referenced this pull request Jul 10, 2026
… time format

Remaining CodeRabbit findings from PR #46 (all real quick-wins):

- streamCompletion leaked an abort listener: opts.signal is reused across the whole
  tool loop, but the 'abort' handler was only auto-removed via {once:true} when it
  actually fired — on normal end/error/timeout it stayed attached, so handlers piled
  up on the shared signal for the loop's lifetime. Added cleanup() (clear timer +
  removeEventListener) run on every terminal path.
- parseDataUrl: guard comma === -1 (truncated data: URL) → return empty bytes instead
  of slicing garbage out of the metadata; and catch decodeURIComponent URIError on a
  stray % → fall back to raw bytes instead of bubbling. Tests for both.
- ChatList formatTime: replaced fixed-offset substring slicing of toLocaleString()
  (breaks/truncates in non-US locales) with explicit Intl date+HH:MM fields; added the
  missing return type.

tsc x2 clean; full suite green; coverage 97/92/96/98.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@alichherawalla

Copy link
Copy Markdown
Contributor Author

Review findings — triaged + addressed

Thanks to Gitar, CodeRabbit, Qodo, and SonarCloud. Went through every comment. Fixed all the substantive findings (each with a test + green gate); noted the ones that are intentionally out of scope.

Fixed (commits on this branch):

  • 🐞 MIME map omitted bmp/heic (Gitar + Qodo) — accepted uploads were mislabelled via the png fallback. Added both; a test now asserts every IMAGE_EXT (imported from the router) resolves to a real image/* type.
  • 🐞 imageMime (RAG vision path) still forced non-png → jpeg (Qodo) — the exact webp mislabel fixed in tools.ts but missed here. Routed through the shared resolver.
  • 🐞 Reasoning captured via a setState-updater side effect (Gitar Bug + CodeRabbit) — unreliable read → Thinking block vanished on reload (the T1f bug). Now mirrored into a ref, read deterministically. Added a red-first integration test (fails pre-fix, passes after).
  • gguf FD leak (CodeRabbit, Major) — closeSync only on success; wrapped in try/finally.
  • streamCompletion abort-listener leak — handler stayed attached on normal end across the reused tool-loop signal; cleanup on every terminal path.
  • parseDataUrl — guard comma === -1 (no garbage decode) + catch decodeURIComponent URIError; tests for both.
  • agenticActive duplicated inline (Major DRY) — reuse the variable.
  • saveSetting inside a state updater — moved to an effect (pure updater).
  • chooseImageModel write-through — added .catch (no silent divergence).
  • ChatList formatTime — replaced locale-fragile fixed-offset slicing with explicit Intl fields.
  • CI: fixed the coverage-gate failure — CI now checks out pro on the matching branch (was always pro main, so pro's new tests were absent while pro source was measured → 60% vs 97%). Now green.

Intentionally out of scope (not defects in this PR):

  • explicit-function-return-type errors — pre-existing and pervasive tree-wide; per CLAUDE.md the prettier + eslint-tightening adoption is its own PR, deliberately not bundled here (the CI lint step is advisory for exactly this reason).
  • text-green-500 (ChatList link) — the desktop design doc sanctions Tailwind green-500/400 as the emerald stand-in.
  • Em dashes in docs/GAPS_BACKLOG.md — internal diagnostic doc, not customer-facing copy.

All fixes: tsc (both configs) clean, full suite green, coverage 97/92/96/98.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/renderer/src/components/__tests__/MemoryChat.reasoning.test.tsx (1)

24-28: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor lint nits flagged by static analysis.

observe/unobserve/disconnect stubs and installApi lack explicit return types, and _a in the addRagMessage mock (line 38) is unused. Note argsIgnorePattern: "^_" is not a default in @typescript-eslint/no-unused-vars — it must be explicitly configured, so the underscore convention used elsewhere in this file (e.g. _args at line 57, which is used) won't automatically suppress this one since _a is never referenced.

🧹 Proposed cleanup
 (globalThis as unknown as { ResizeObserver?: unknown }).ResizeObserver ??= class {
-  observe() {}
-  unobserve() {}
-  disconnect() {}
+  observe(): void {}
+  unobserve(): void {}
+  disconnect(): void {}
 };
-  const addRagMessage = vi.fn(async (..._a: AddRagArgs) => {});
+  const addRagMessage = vi.fn(async () => {});

Also applies to: 36-38

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/components/__tests__/MemoryChat.reasoning.test.tsx` around
lines 24 - 28, Add explicit return types to the ResizeObserver stub methods
observe, unobserve, and disconnect, and to installApi. Remove the unused _a
parameter from the addRagMessage mock (or otherwise avoid declaring an
unreferenced argument), since the lint configuration does not ignore
underscore-prefixed variables by default.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/mcp-parse-data-url.ts`:
- Around line 26-27: Update the metadata check in the data URL parser to
recognize base64 only as a standalone metadata token, not as a substring within
values such as foo=notbase64 or foo=base64; preserve the existing base64
decoding behavior otherwise. Add a regression test covering the exact foo=base64
case and verifying the payload is not decoded as base64.

---

Nitpick comments:
In `@src/renderer/src/components/__tests__/MemoryChat.reasoning.test.tsx`:
- Around line 24-28: Add explicit return types to the ResizeObserver stub
methods observe, unobserve, and disconnect, and to installApi. Remove the unused
_a parameter from the addRagMessage mock (or otherwise avoid declaring an
unreferenced argument), since the lint configuration does not ignore
underscore-prefixed variables by default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a8aaecfa-6ad7-4498-a806-523b81d8f6af

📥 Commits

Reviewing files that changed from the base of the PR and between b7f7034 and 9f09181.

📒 Files selected for processing (6)
  • src/main/__tests__/mcp-parse-data-url.test.ts
  • src/main/llm/stream.ts
  • src/main/mcp-parse-data-url.ts
  • src/renderer/src/components/ChatList.tsx
  • src/renderer/src/components/MemoryChat.tsx
  • src/renderer/src/components/__tests__/MemoryChat.reasoning.test.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/tests/mcp-parse-data-url.test.ts
  • src/renderer/src/components/ChatList.tsx
  • src/main/llm/stream.ts
  • src/renderer/src/components/MemoryChat.tsx

Comment thread src/main/mcp-parse-data-url.ts Outdated
@gitar-bot

gitar-bot Bot commented Jul 10, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 resolved / 2 findings

Implements extensive architectural hardening including automated tooling for dead-code and dependency management while unifying MIME handling and core state persistence. Resolves multiple reported image attachment and reasoning state bugs with no remaining open findings.

✅ 2 resolved
Edge Case: HEIC/BMP image attachments fall back to image/png in vision data URL

📄 src/main/mime.ts:11-25 📄 src/main/files-classify.ts:7 📄 src/main/tools.ts:349-354
classifyUpload treats bmp and heic as valid image uploads (IMAGE_EXT includes them), but the shared EXT_MIME map in mime.ts has no entry for bmp or heic. In tools.ts, image attachments are labelled via mimeFromExt(...) which falls back to image/png for unknown extensions. So a .heic/.bmp attachment is base64-encoded with an image/png MIME header while carrying HEIC/BMP bytes — the same class of mislabel this PR fixes for .webp (a vision model may reject the mismatched declared type). Consider either adding bmp: 'image/bmp' (and a HEIC entry / conversion) to the map, or excluding formats from the picker that cannot be correctly labelled/decoded.

Bug: Reasoning captured via side effect inside setState updater is unreliable

📄 src/renderer/src/components/MemoryChat.tsx:913-920 📄 src/renderer/src/components/MemoryChat.tsx:987-993 📄 src/renderer/src/components/MemoryChat.tsx:1003 📄 src/renderer/src/components/MemoryChat.tsx:196-199 📄 src/renderer/src/components/MemoryChat.tsx:557-571
Both persistence sites capture the streamed reasoning by mutating an outer variable inside the setConvMessages updater and then reading it on the very next line:

let toolReasoning: string | undefined;
setConvMessages(convId, prev => prev.map(m => {
  if (m.id !== toolStreamId) return m;
  toolReasoning = m.reasoning;      // side effect inside updater
  return { ...m, content: answer, ... };
}));
const toolCtxWithReasoning = buildAssistantContext(toolCtx, { reasoning: toolReasoning });

setConvMessages wraps a normal setState(updater) (line 196-199). React does not guarantee the updater runs synchronously during the dispatch call — it only runs the updater eagerly when the fiber's update queue is empty (the bail-out optimization); otherwise it is deferred to the render phase. When any update is still pending on messagesByConv (e.g. a late onRagStream delta, another tab's stream, StrictMode double-invoke), toolReasoning/ragReasoning are read as undefined, so buildAssistantContext drops the reasoning and the persisted context blob has no reasoning key. On reload the 'Thinking' block silently vanishes — the exact behavior this change (T1f) is meant to fix. Because it depends on React's eager-bail timing, the failure is intermittent and not caught by the pure message-persistence unit tests.

Capture the value outside the updater instead of relying on updater side effects — e.g. mirror the accumulated reasoning in a ref (keyed by streamId) updated in the onRagStream handler, and read that ref when persisting.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Auto-apply Compact
gitar auto-apply:on         
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

alichherawalla and others added 13 commits July 15, 2026 14:47
…ed, D26)

autoConfigure passes the setup-vocab kind 'voice' to setActiveModalChoice, but
that function gated on isModalKind, which only accepts the storage vocab
('speech'/'image'/'transcription'). So the 'voice' call returned { success:false }
(swallowed by the try/catch) and Kokoro TTS was never set active — setup claimed
"voice is set up" while the model showed inactive.

Fix: setActiveModalChoice normalizes the kind through modalityForModel — the one
dispatch, now idempotent on 'speech' — so BOTH the setup 'voice' and the
dispatched 'speech' (from activateModel) activate TTS. One normalizer, both
vocabularies (DRY).

Test: active-models.test.ts gains the modalityForKind('speech')==='speech'
idempotency (red on HEAD — no 'speech' case); set-active-modal-choice.test.ts is
a source-contract guard that setActiveModalChoice no longer gates on isModalKind
and normalizes via modalityForModel (red on HEAD — had the isModalKind gate).
Full flow is the on-device check. Unit suite 2138 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The download loop renamed `<file>.part` to its final name (and recorded it
installed + activatable) with NO integrity check. A server that closed the
connection early, or any short read, left a TRUNCATED file that got promoted —
then llama-server died on load with a blank "Chat model Down", the exact
no-reason failure CLAUDE.md warns about. (importLocalModel already validated;
the download path never did.)

Fix: downloadIntegrityError (models/download-verify.ts) gates the promotion —
rejects when written < the server-reported content-length, and for a .gguf when
the magic header / min size fails. downloadModel throws on it before the rename,
so a bad file surfaces as a visible "failed" instead of a silently-installed
broken model.

Test: download-verify.test.ts over real temp files — truncated / corrupt-magic /
too-small are rejected; complete valid GGUF, non-GGUF, and no-length pass.
Falsified (neutralizing the check reds the 3 rejection cases). Unit suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Downloading the same model twice (double-click, or Retry while it's already
downloading) set controllers[id] a second time — two writers raced into the same
`<file>.part` (interleaved → corrupt), and the second controller overwrote the
first, so a later Cancel/Clear aborted the wrong download and left an orphan.

Fix: downloadModel no-ops (`{ success:false, error:'already downloading' }`) if a
download for that id is already in flight — checked before any status emit or
controller registration.

Test: download-reentrancy.test.ts — source-contract guard that the check exists
and runs before controllers.set (red on HEAD). On-device check in DEVICE_TEST_LOG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
D13 (QA claim: cancel-partial finalize strands the activity label) is a FALSE
POSITIVE: the activity/thinking block renders only while message.streaming
(MemoryChat.tsx:1721), and every finalize sets streaming:false, so the leftover
`activity` field is never shown. A test I wrote "passed" only because
streaming:false hid the label — a false-green — so I backed it out rather than
ship theater. Kept the one real find from the investigation: an
aria-label="Stop generating" on the Stop button, which had no accessible name.

D7 similarly dismissed (see DEVICE_TEST_LOG): the /health result IS honored
(setup.ts:306 `return { success: ok }`); the QA agent only read to line 279.

Both recorded honestly in DEVICE_TEST_LOG with the disproving evidence.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
deleteModel cleared a per-modality selection only when `modals[k] === modelId`.
But image picks are stored by PRIMARY FILENAME (setActiveModalChoice maps id ->
filename for image), so deleting the active image model never matched → the
active-modalities pointer dangled at a now-deleted file, and the next image gen
failed to load with no clear reason.

Fix: modalSelectionMatches (pure, in catalog-logic) matches a stored selection by
id OR primary filename; deleteModel passes the entry's primaryFileName so the
image modality is cleared too.

Test: catalog-logic.test.ts — the matcher matches by filename (the case HEAD's
id-only compare missed) and by id, and rejects null/mismatch. Full delete flow is
the on-device check. tsc + suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…D23)

deleteRagConversation deleted only the rag_conversations row. rag_messages
orphaned (FKs are off, so the ON DELETE CASCADE never fired) and the
conversation's generated artifacts (keyed by conversationId) lingered in the
library forever — still openable, unbounded disk growth. Same missing lifecycle
tie D20 fixed for projects.

Fix: deleteRagConversation deletes the conversation's rag_messages explicitly;
the rag:delete-conversation handler also calls the new
deleteArtifactsForConversation (mirrors deleteArtifactsForProject).

Test: conversation-delete-cascade.dbtest.ts — real temp SQLite + real artifact
files; runs the exact two calls the handler makes, asserts rows + artifact gone.
Red when the fixes are reverted (falsified). DB suite 69/69; unit suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
E Security Rating on New Code (required ≥ A)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

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.

1 participant