Skip to content

v2.5.6 — Live Preview editor, community tool registry, note-delete + OAuth-cancel fixes - #92

Merged
ddutchie merged 18 commits into
mainfrom
ddutchie/aichattweaks
Jul 24, 2026
Merged

ddutchie merged 18 commits into
mainfrom
ddutchie/aichattweaks

Conversation

@ddutchie

@ddutchie ddutchie commented Jul 24, 2026 •

Copy link
Copy Markdown
Owner

What does this PR do?

Ships everything in the v2.5.6 changelog: the Live Preview note editor, AI/Chat settings additions (temperature + models.dev context detection), the community tool registry (fetch/cache + Browse & 1-click install, backed by the new cairn-community repo), plus fixes for a note-deletion race and MCP OAuth sign-in cancellation.

Type of change

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

Summary of changes (from changelogs/v2.5.6.md)

Added

  • Temperature control in AI & Chat settings (presets + custom), matching Coding Agent settings.
  • Automatic context-window detection via models.dev in AI & Chat + Coding Agent settings — Auto applies the model's real context size, fuzzy-matches proxy/gateway model ids, cached locally/offline.
  • Live Preview note editor — hides markdown markers on non-cursor lines (bold/italic/code/headings/bullets/links), matched to Read-mode sizing; raw markdown still saved to disk.
  • Live Preview also styles ==highlights==, [[wikilinks]] (accent chips) and > blockquotes, and renders callouts (> [!note], …) inline.
  • Community tool registry — fetch/cache layer (electron/lib/community-registry.ts) for an ETag-cached, offline-resilient manifest.json, Zod-validated.
  • Browse Community tools — Settings → Tools → Browse: search + tag-filtered MCP servers (Jira, Linear, Notion, GitHub, monday, Sentry, Asana, Stripe, HubSpot, Canva, Cloudflare, Vercel, Zapier…) and HTTP services, with brand logos, 1-click install (disabled for review), API-key prompts (keychain-stored), OAuth "install → Connect", and an Update badge when the registry ships a newer version.

Changed

  • Settings reflow gracefully in a narrow window (container queries); AI Provider toggle wraps.
  • Corrected the AI Provider description (removed mobile-only "Apple Intelligence" reference).

Fixed

  • Outbound HTTPS in dev — dev CSP now permits https: (was blocking models.dev lookups).
  • Note deletion on link — link_note_to_task / unlink_note_from_task now write the .md file inside the mcp_active_writes lock, so the separate-process file-watcher no longer misreads the relocation as a delete and purges the row. Regression test added.
  • MCP OAuth cancellation — denying consent shows a clear "cancelled" message (was "response missing/invalid"); a Cancel button replaces the stuck "Waiting for browser…" state; unfinished sign-ins time out after 10 min with a clear message.

Screenshots / recording

UI changes (Live Preview, Browse Community, Settings reflow, OAuth Cancel button) — screenshots to be attached. Not captured in this environment; flagging for reviewer.

Checklist

  • npm run type-check:all passes
  • npm run lint passes (0 errors; 3 pre-existing set-state-in-effect warnings consistent with existing Settings panels)
  • npm test passes — targeted suites green (registry, oauth-loopback, link-note-task: 21 tests); full npm test not run in this environment
  • npm run test:e2e — not run in this environment
  • No hardcoded colours — CSS variables only
  • No text-[Npx] pixel font classes — rem equivalents only
  • New IPC handlers wrapped in handle() and return IpcResult<T> (registry:fetch/refresh, tools:cancelMcpAuth)
  • No new DB migrations (registry cache is a userData JSON file, not SQLite)
  • New SQL — n/a (no new queries; install reuses existing saveMcpServer/saveCustomService)
  • No new runtime dependencies (zod already present)
  • MCP tools — n/a (no new agent-facing MCP tools; these are renderer IPC channels)
  • No --external: compile-flag changes

Notes for reviewer

  • Manual QA pending: the UI (Live Preview, Browse Community, OAuth Cancel) is verified by type-check + targeted unit tests only — not a running-app pass in this environment. Please exercise before merge/release.
  • Companion repo: the catalog data lives in ddutchie/cairn-community (manifest.json + JSON Schema + CI validator). Cairn fetches it at runtime; 15 MCP connectors + 3 HTTP services seeded, all endpoints verified live.
  • link_note_to_task fix requires an MCP-server restart to take effect for the standalone binary.
  • Follow-ups already filed: OAuth for custom HTTP services (Phases A–C) — see the "Connectors: non-MCP services + OAuth-for-HTTP plan" note. Not in this PR.

Summary by CodeRabbit

  • New Features
    • Added AI Temperature controls (presets + custom) and automatic context-window detection with Auto/manual switching for AI and Agent settings.
    • Added Settings → Tools → Browse community tools with search, tag filtering, endpoint previews, install/update, and credential prompts.
    • Improved markdown Live Preview styling by hiding markers on non-cursor lines and enhancing headings, links, lists, highlights, wikilinks, and blockquotes (and callouts when available).
  • Bug Fixes
    • Prevented note loss during note↔task linking/unlinking; improved sign-in cancellation, timeout messaging, and browser-step controls.
    • Strengthened delete/tombstone handling to avoid sync-related note disappearance; projector now respects tombstones.
  • Usability
    • Refined Settings layout for narrow windows and updated AI Provider guidance text.

ddutchie added 10 commits July 23, 2026 19:29
…at and Coding Agent

- Add Temperature control to AI & Chat settings (matching Coding Agent)
- Add Context window auto-detection via models.dev for both AI & Chat
  (Cloud/Local API) and Coding Agent settings, with fuzzy id matching for
  proxy/gateway model ids and a persisted contextAuto override flag
- Auto-apply detected context on model change; manual value/preset disables
  Auto, tapping Auto re-enables
- Layer localStorage under backend agent config on hydrate so contextAuto
  survives restarts
- Correct AI Provider description (drop mobile-only Apple Intelligence)
- Reflow settings rows on narrow windows via container queries
- Allow outbound https: in dev CSP so the models.dev fetch succeeds
Write mode now hides markdown syntax markers on every line except the one
the cursor is on, and renders formatting inline — headings at true size,
bold/italic/code, bullets as •, links as display text, ==highlight==,
[[wikilink]] chips, and blockquote borders — matched to Read mode's
.prose-cairn styles. Raw markdown is unchanged on disk.

Also fixes in-editor formatting that never worked: the theme referenced
.tok-* classes that CodeMirror never emitted. Added a HighlightStyle
mapping Lezer tags to those classes.

- src/lib/livePreview.ts: marker-hiding + inline decoration extension
- markdown-editor.tsx: HighlightStyle + livePreview compartment/prop
- note-editor.tsx: enable Live Preview in Write mode
- livePreview.component.test.tsx: 6 tests (ordering, classes, active-line reveal)
Obsidian-style callouts (> [!note] …) now render as their coloured box
inline in Write mode, reusing the Read-mode <Callout> component and
NoteMarkdownPreview for the body. Cursor outside the callout → widget;
cursor inside → raw source for editing (click-to-edit).

Block-level replace decorations can't be provided via a ViewPlugin, so
callouts use a dedicated StateField (calloutField); inline decorations
stay in the ViewPlugin. Both share findCalloutBlocks() so inline passes
skip lines a widget covers (avoiding overlapping decorations). React
roots mount in WidgetType.toDOM() and unmount via queueMicrotask.

- src/lib/callout-widget.tsx: CalloutWidget + parseCalloutSource
- src/lib/livePreview.ts: calloutField StateField + inline-pass guards
- livePreview.component.test.tsx: 11 tests (widget in/out, parse)
Inline-SVG brand glyphs keyed by the cairn-community manifest 'logo' id
(jira, confluence, linear, notion, monday, github, sentry, brave,
hackernews) with a generic plug fallback. Scaffolding for Registry 2's
Browse Community picker; not yet wired into the UI.
Fetches the cairn-community catalog (manifest.json) with a conditional
GET (stored ETag), caches the last good manifest in userData for
offline/instant reads, and Zod-validates the payload (https-only URLs,
required fields) mirroring cairn-community/schema.json. Fail-soft: any
network/parse error serves the cache; only a failure with no cache
surfaces an error.

- electron/lib/community-registry.ts (fetch + cache + validate)
- electron/ipc/community-registry-handlers.ts (registry:fetch / :refresh)
- preload registry namespace (auto-typed via ElectronAPI)
- registry manifest types in src/types/index.ts
- 9 unit tests (200/304/offline/no-cache/cache-first/refresh)

Read/cache layer only; browse + install UI is Registry 2.
Settings -> Tools -> Browse opens the cairn-community catalog: search +
tag-filtered MCP servers and HTTP services, each with brand logo
(ConnectorLogo), blurb, and the exact endpoint shown before install.

- BrowseCommunityModal.tsx: fetch (cache-first) + refresh, search, tag
  chips, install/update/installed states, secret prompt for <API_KEY>
  placeholder headers, OAuth badge
- tools slice: installCommunityMcp / installCommunityService — install
  DISABLED with source=community + communityId + version; secrets stored
  in the keychain (secret:// refs), never persisted raw; re-install onto
  the same id preserves keychain secrets (update path)
- ToolsSettings: Browse Community card + button, refetch on close
- Update-available badge when installed version != manifest version

Builds on Registry 1 (window.electron.registry). type-check:all +
compile clean; 14 registry tests green.
- oauth-loopback: parse OAuth error responses (RFC 6749) — access_denied
  now rejects with an OAuthDeniedError + friendly 'cancelled' page instead
  of a misleading 'missing/invalid' 400; generic errors surface their
  description; the 10-min TTL rejects with a clear 'timed out' message
- mcp-oauth: track active loopback listeners per server; cancelServerAuth(id)
  tears one down (rejects the pending callback -> renderer gets a cancelled
  completion)
- IPC tools:cancelMcpAuth + preload cancelMcpAuth
- McpAuthButton: while waiting, show a Cancel button (was a dead
  'Waiting for browser…' state)
- 4 new loopback tests (deny / generic error / cancel reason)

Also expands ConnectorLogo with glyphs for the 7 new cairn-community
connectors (asana, stripe, hubspot, canva, intercom, cloudflare, vercel).
@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ddutchie, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 8 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7db3dcf7-1c85-4925-928f-49edfc9bb079

📥 Commits

Reviewing files that changed from the base of the PR and between 89143a0 and b36e7de.

📒 Files selected for processing (13)
  • changelogs/v2.5.6.md
  • docs/plans/sync-lifecycle-hardening.md
  • electron/db/queries.test.ts
  • electron/db/queries.ts
  • electron/ipc/tools.ts
  • electron/lib/community-registry.test.ts
  • electron/lib/community-registry.ts
  • electron/lib/mcp-oauth.ts
  • electron/preload.ts
  • src/components/settings/ToolsSettings.tsx
  • src/components/settings/tools/BrowseCommunityModal.tsx
  • src/components/settings/tools/ConnectorLogo.tsx
  • src/types/index.ts
📝 Walkthrough

Walkthrough

Version 2.5.6 adds community tool discovery and installation, Markdown live preview rendering, automatic model context detection, improved MCP OAuth cancellation, tombstone-based note deletion, safer note-link writes, responsive settings layout changes, and development HTTPS connectivity.

Changes

Community registry and tool installation

Layer / File(s) Summary
Registry contracts and manifest validation
src/types/index.ts, electron/lib/community-registry.ts, electron/lib/community-registry.test.ts
Defines registry types, validates individual manifest entries, and tests malformed-entry filtering.
Manifest cache and network retrieval
electron/lib/community-registry.ts, electron/lib/community-registry.test.ts
Adds cache-first reads, ETag revalidation, forced refresh, atomic writes, timeout handling, stale-cache fallback, and coverage for these paths.
Registry IPC bridge and startup wiring
electron/ipc/community-registry-handlers.ts, electron/main.ts, electron/preload.ts
Exposes registry fetch/refresh APIs and registers handlers at startup.
Community browser and installation persistence
src/components/settings/tools/*, src/store/slices/tools.ts, src/components/settings/ToolsSettings.tsx
Adds searchable/tag-filtered browsing, safe connector logos, secret prompts, install/update actions, and disabled community tool/service persistence.

Markdown editor live preview

Layer / File(s) Summary
Live preview decorations and callout widgets
src/lib/livePreview.ts, src/lib/callout-widget.tsx
Hides Markdown markers on inactive lines, styles inline syntax, renders bullets and blockquotes, and defines callout widget rendering behind a feature flag.
Editor integration and validation
src/components/notes/markdown-editor.tsx, src/components/notes/note-editor.tsx, src/lib/livePreview.component.test.tsx
Enables live preview in Write mode, applies Markdown token styling, supports runtime toggling, and tests active-line and inline behavior.

Model context detection and settings

Layer / File(s) Summary
Models.dev lookup and configuration contracts
src/lib/models-dev.ts, src/lib/constants.ts, src/store/slices/ui.ts
Adds cached, fuzzy model-ID context lookup and contextAuto defaults and configuration fields.
AI and agent context controls
src/components/settings/AISettings.tsx, src/components/settings/AgentSettings.tsx, src/components/settings/shared.tsx
Adds automatic context detection, manual override behavior, Auto controls, and an AI temperature stepper.
Configuration hydration and responsive settings
src/store/index.ts, src/components/settings/settings-view.tsx, src/components/settings/shared.tsx
Preserves local agent fields during hydration and reflows settings rows for narrow containers.

MCP sign-in cancellation and status

Layer / File(s) Summary
Loopback OAuth outcome handling
electron/lib/oauth-loopback.ts, electron/lib/oauth-loopback.test.ts
Adds explicit cancellation, authorization-error, and timeout results with dedicated error handling and tests.
MCP OAuth cancellation control
electron/lib/mcp-oauth.ts, electron/ipc/tools.ts, electron/preload.ts
Tracks active loopback listeners and exposes server-auth cancellation through IPC.
Browser-step cancellation UI
src/components/settings/tools/McpAuthButton.tsx
Shows a browser-waiting state with a Cancel action during sign-in.

Note deletion, linking, and runtime fixes

Layer / File(s) Summary
Soft deletion and tombstone projection
electron/db/queries.ts, electron/shared/db-mappers.ts, electron/main.ts, electron/db/queries.test.ts, shared/sync/engine.test.ts
Changes note deletion to idempotent tombstones, hides deleted rows from normal reads, preserves tombstone visibility for sync projection, and updates deletion tests.
Note-link lock scope and regression coverage
electron/mcp/tools/tasks.ts, electron/mcp/tools/link-note-task.test.ts
Keeps the MCP write lock held through markdown persistence and verifies link/unlink behavior and lock lifetime.
Development connectivity and release documentation
electron/lib/protocol.ts, changelogs/v2.5.6.md, docs/plans/sync-lifecycle-hardening.md
Allows HTTPS in the development renderer CSP and records release and sync-lifecycle changes.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • ddutchie/cairn#66: Modifies the same note-linking code paths and write-lock behavior.
  • ddutchie/cairn#73: Modifies the MCP OAuth and loopback authentication surfaces.
  • ddutchie/cairn#87: Modifies related tombstone and note deletion synchronization behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.06% 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 title is concise and accurately summarizes the main themes of the change set: Live Preview, community registry, note deletion, and OAuth cancellation.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ddutchie/aichattweaks

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.

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

🧹 Nitpick comments (4)
src/lib/livePreview.ts (1)

173-272: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: collapse the repeated full-tree traversals in the rebuild path.

buildDecorations runs on every doc change, selection move, and viewport scroll, and each rebuild performs four separate syntaxTree(...).iterate(...) passes per visible range plus calloutWidgetLineSet, which itself walks the entire tree (as does buildCalloutDecorations in the sibling field). For large notes this repeated traversal is avoidable. Consider a single iterate with a switch (node.name) for the mark/blockquote/link/list passes, and computing the callout block set once per rebuild and threading it through. Not required for correctness.

🤖 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/lib/livePreview.ts` around lines 173 - 272, Optionally optimize
buildDecorations by consolidating the separate syntaxTree(...).iterate passes
for hidden marks, blockquotes, links, and list markers into one traversal using
node-name branching. Compute calloutWidgetLineSet once per rebuild and reuse it
across the traversal and scanInlinePatterns, preserving all existing filtering
and decoration behavior.
src/lib/models-dev.ts (1)

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

Consider unit tests for the fuzzy-matching logic.

normalizeId/getNormIndex/lookup implement a fairly intricate regex-based normalization chain (region/vendor prefixes, separator swapping, suffix stripping) with no accompanying test file in this cohort. Given how easily proxy/gateway id formats can regress this logic silently, a small table-driven test (e.g. playground-claude-opus-4-8, us.anthropic.claude-3-opus-20240229-v1:0, gpt-4.1 ↔ gpt-4-1) would guard against future regressions.

🤖 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/lib/models-dev.ts` around lines 44 - 93, The fuzzy model-ID matching
logic lacks regression coverage. Add a focused, table-driven unit test for
normalizeId and lookup that verifies gateway/vendor and region prefix removal,
date/version and variant suffix stripping, and separator-swapping matches such
as playground-claude-opus-4-8, us.anthropic.claude-3-opus-20240229-v1:0, and
gpt-4.1/gpt-4-1.
src/components/settings/AISettings.tsx (1)

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

Extract the duplicated models.dev auto-detect logic into a shared hook.

AISettings.tsx and AgentSettings.tsx each implement the same ~25-line effect (detect → cancellation guard → stale-closure-safe auto-apply via useCairnStore.getState()), the same "idle" | "loading" | "detected" | "not_found" union (also re-declared a third time in shared.tsx), and the same hardcoded 128000 fallback that duplicates DEFAULT_CONTEXT_LIMIT already exported from src/lib/models-dev.ts. A shared hook (e.g. useModelContextAutoDetect(modelId, contextAuto, contextLimit, applyLimit)) would remove the duplication and centralize the constant/type.

  • src/components/settings/AISettings.tsx#L45-L76: replace this effect + local detectedContext/autoState state with the shared hook; also update the 128000 fallback at lines 209/222 to import DEFAULT_CONTEXT_LIMIT from @/lib/models-dev.
  • src/components/settings/AgentSettings.tsx#L435-L463: replace this effect + local detectedContextAgent/autoStateAgent state with the same shared hook; also update the 128000 fallback at lines 548/562 to import DEFAULT_CONTEXT_LIMIT from @/lib/models-dev.
♻️ Sketch of a shared hook
// src/lib/use-model-context-auto-detect.ts
import { useEffect, useState } from "react";
import { contextLimitForModel } from "`@/lib/models-dev`";

export type AutoDetectState = "idle" | "loading" | "detected" | "not_found";

export function useModelContextAutoDetect(
  modelId: string | undefined,
  enabled: boolean,
  contextAuto: boolean,
  currentContextLimit: number | undefined,
  applyDetected: (n: number) => void,
) {
  const [detectedContext, setDetectedContext] = useState<number | null>(null);
  const [autoState, setAutoState] = useState<AutoDetectState>("idle");

  useEffect(() => {
    if (!enabled) return;
    let cancelled = false;
    const id = (modelId ?? "").trim();
    if (!id) { setDetectedContext(null); setAutoState("idle"); return; }
    setAutoState("loading");
    contextLimitForModel(id, 0).then((n) => {
      if (cancelled) return;
      const found = n > 0 ? n : null;
      setDetectedContext(found);
      setAutoState(found ? "detected" : "not_found");
      if (found && contextAuto && currentContextLimit !== found) applyDetected(found);
    });
    return () => { cancelled = true; };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [modelId, enabled, contextAuto]);

  return { detectedContext, autoState };
}
🤖 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 45 - 76, Extract the
duplicated model context detection into a shared hook, such as
useModelContextAutoDetect, centralizing the AutoDetectState type and
cancellation-safe detection/auto-apply behavior. In
src/components/settings/AISettings.tsx lines 45-76 and
src/components/settings/AgentSettings.tsx lines 435-463, replace each local
effect and detection state with the hook, preserving cloud-only enablement and
each component’s apply callback. In both files, replace the hardcoded 128000
fallbacks at the specified locations with the imported DEFAULT_CONTEXT_LIMIT
from `@/lib/models-dev`.
electron/lib/community-registry.ts (1)

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

Migrate deprecated Zod v4 schemas.

z.string().url() and .passthrough() are deprecated in Zod 4. Move the URL checks to z.string().url() -> z.url() where appropriate, and replace .passthrough() here with z.looseObject(...) or one of .loose() to avoid deprecation warnings/future breakage.

🤖 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/lib/community-registry.ts` around lines 86 - 104, The schemas use
deprecated Zod v4 APIs. Update URL validations in the visible registry schemas,
including baseUrl and apiUrl, to use z.url() while preserving the existing HTTPS
restriction; replace the related .passthrough() usage in the surrounding schema
definitions with z.looseObject(...) or the equivalent .loose() form.
🤖 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/lib/community-registry.ts`:
- Around line 116-123: Update manifestSchema and parseManifest so invalid
individual mcpServers and services entries are filtered out instead of causing
whole-manifest parsing to fail. Use element-level validation with safe parsing,
preserve valid entries and manifest-level validation, and update the existing
malformed-entry test to assert the bad entry is dropped rather than rejected.
- Line 113: Update the community manifest schema’s homepage field to validate
only HTTP(S) URLs rather than arbitrary strings, preventing unsafe schemes from
reaching BrowseCommunityModal.tsx; apply the same URL-scheme validation to the
service definition’s apiKeyUrl field.

In `@electron/lib/mcp-oauth.ts`:
- Around line 353-368: Update cancelServerAuth so it records whether a loopback
listener was removed, captures the result of cancelPendingForServer(serverId),
and returns true when either cancellation succeeds. Preserve the existing
cleanup for both active loopbacks and pending deep-link attempts.
- Around line 245-248: Update the loopback registration flow around
activeLoopbacks to close and cancel any existing listener for the same server id
before replacing its map entry. Ensure the prior flow’s cleanup cannot remove or
invalidate the newly registered listener, while preserving cancellation behavior
for the current browser sign-in.

In `@src/lib/livePreview.ts`:
- Around line 104-125: Update findCalloutBlocks so it only processes Blockquote
nodes whose parent syntax node is not another Blockquote, skipping nested
callout blockquotes before parsing and emitting ranges. Preserve the existing
parseCalloutSource and decoration data behavior for eligible outer blockquotes.

---

Nitpick comments:
In `@electron/lib/community-registry.ts`:
- Around line 86-104: The schemas use deprecated Zod v4 APIs. Update URL
validations in the visible registry schemas, including baseUrl and apiUrl, to
use z.url() while preserving the existing HTTPS restriction; replace the related
.passthrough() usage in the surrounding schema definitions with
z.looseObject(...) or the equivalent .loose() form.

In `@src/components/settings/AISettings.tsx`:
- Around line 45-76: Extract the duplicated model context detection into a
shared hook, such as useModelContextAutoDetect, centralizing the AutoDetectState
type and cancellation-safe detection/auto-apply behavior. In
src/components/settings/AISettings.tsx lines 45-76 and
src/components/settings/AgentSettings.tsx lines 435-463, replace each local
effect and detection state with the hook, preserving cloud-only enablement and
each component’s apply callback. In both files, replace the hardcoded 128000
fallbacks at the specified locations with the imported DEFAULT_CONTEXT_LIMIT
from `@/lib/models-dev`.

In `@src/lib/livePreview.ts`:
- Around line 173-272: Optionally optimize buildDecorations by consolidating the
separate syntaxTree(...).iterate passes for hidden marks, blockquotes, links,
and list markers into one traversal using node-name branching. Compute
calloutWidgetLineSet once per rebuild and reuse it across the traversal and
scanInlinePatterns, preserving all existing filtering and decoration behavior.

In `@src/lib/models-dev.ts`:
- Around line 44-93: The fuzzy model-ID matching logic lacks regression
coverage. Add a focused, table-driven unit test for normalizeId and lookup that
verifies gateway/vendor and region prefix removal, date/version and variant
suffix stripping, and separator-swapping matches such as
playground-claude-opus-4-8, us.anthropic.claude-3-opus-20240229-v1:0, and
gpt-4.1/gpt-4-1.
🪄 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: 35ff6d6e-c085-444e-96ef-8e02b39e377d

📥 Commits

Reviewing files that changed from the base of the PR and between 5ed7624 and 7af9d0d.

📒 Files selected for processing (32)
  • changelogs/v2.5.6.md
  • electron/ipc/community-registry-handlers.ts
  • electron/ipc/tools.ts
  • electron/lib/community-registry.test.ts
  • electron/lib/community-registry.ts
  • electron/lib/mcp-oauth.ts
  • electron/lib/oauth-loopback.test.ts
  • electron/lib/oauth-loopback.ts
  • electron/lib/protocol.ts
  • electron/main.ts
  • electron/mcp/tools/link-note-task.test.ts
  • electron/mcp/tools/tasks.ts
  • electron/preload.ts
  • src/components/notes/markdown-editor.tsx
  • src/components/notes/note-editor.tsx
  • src/components/settings/AISettings.tsx
  • src/components/settings/AgentSettings.tsx
  • src/components/settings/ToolsSettings.tsx
  • src/components/settings/settings-view.tsx
  • src/components/settings/shared.tsx
  • src/components/settings/tools/BrowseCommunityModal.tsx
  • src/components/settings/tools/ConnectorLogo.tsx
  • src/components/settings/tools/McpAuthButton.tsx
  • src/lib/callout-widget.tsx
  • src/lib/constants.ts
  • src/lib/livePreview.component.test.tsx
  • src/lib/livePreview.ts
  • src/lib/models-dev.ts
  • src/store/index.ts
  • src/store/slices/tools.ts
  • src/store/slices/ui.ts
  • src/types/index.ts

Comment thread electron/lib/community-registry.ts Outdated
Comment thread electron/lib/community-registry.ts
Comment thread electron/lib/mcp-oauth.ts
Comment thread electron/lib/mcp-oauth.ts
Comment thread src/lib/livePreview.ts
ddutchie added 5 commits July 23, 2026 22:21
- registry: validate homepage + apiKeyUrl as https URLs (they render as
  anchor hrefs — block javascript:/data: from a compromised manifest)
- registry: drop malformed manifest entries individually instead of
  failing the whole parse (an insecure/invalid entry no longer blanks the
  catalog for everyone); tests updated to assert drop-not-throw
- mcp-oauth: startServerAuth now cancels a prior loopback listener (via
  cancelServerAuth) before registering a new one, and the finally-cleanup
  only deletes the map entry if it still points at its own listener — a
  re-initiated sign-in can no longer be orphaned/uncancellable
- mcp-oauth: cancelServerAuth returns true when a deep-link pending attempt
  was cancelled (not just a loopback), so tools:cancelMcpAuth reports
  { cancelled: true } accurately
- livePreview: findCalloutBlocks skips a blockquote nested inside another
  blockquote, preventing overlapping block-replace decoration ranges that
  throw during measurement
CI runs eslint with --max-warnings 0. These three effects reset state
synchronously on purpose (model-id/context detection reset, community
catalog load), so add targeted eslint-disable-line comments matching the
existing convention in the file.
Inline callout block widgets cause cursor drift (cursor lands lines
below the click) and unreliable click-to-enter, due to CodeMirror
measuring the React-mounted widget at ~0 height before it paints.
Attempted fixes (rAF requestMeasure, estimatedHeight=-1, ignoreEvent
false) did not fully resolve it in live testing.

Gate behind CALLOUTS_ENABLED=false so callout blockquotes fall back to
ordinary Tier 1 blockquote rendering. The widget implementation stays
intact for a future fix. Tracked on the Cairn board.

- livePreview.ts: CALLOUTS_ENABLED flag; calloutField/theme only added
  when enabled; calloutWidgetLineSet returns empty when disabled
- callout-widget.tsx: keep the attempted measurement fixes for later
- test: replace widget-behaviour tests with a disabled-state assertion;
  keep parseCalloutSource unit tests
- changelog: drop the callout entry (not shipping)
Deleting a note on desktop physically removed the row (DELETE FROM notes)
and relied on the sync engine reconstructing a throwaway tombstone shell so
peers wouldn't resurrect it. Combined with a stale device's oplog this could
let a deleted note reappear or a freshly-created note vanish.

deleteNote now soft-deletes: UPDATE notes SET deleted_at = COALESCE(...) so
the row survives as a durable tombstone, matching mobile. The AFTER UPDATE
capture trigger already stages a 'delete' op (keys off NEW.deleted_at), so
peers tombstone via the normal drainPending delete branch — the shell hack
is no longer needed for the desktop-delete path.

- getNoteById now filters deleted_at IS NULL so MCP append/patch/tag and the
  disk projector can't resurrect a tombstoned note; added
  getNoteByIdIncludingTombstoned for projectNoteToDisk, which must still see
  the tombstone to remove the orphan .md file.
- toNote mapper exposes deletedAt.
- Live list/search reads already filtered deleted_at IS NULL (the old comment
  claiming otherwise was stale), so no ghost rows leak.

Groundwork for delete-wins reconciliation (docs/plans/sync-lifecycle-hardening.md).
All 1298 tests pass; type-check:all + compile clean.
…lyphs

Connector logos now come from the compiled cairn-community manifest as
inline iconSvg (allowlist-sanitized upstream at CI build time), rendered
inline and tinted via brandColor. Deletes the 17 hardcoded brand-glyph
cases — new connectors get their logo from the registry without an app
release. Falls back to a generic glyph (MCP mark for MCP servers, plug for
HTTP services) when iconSvg is absent.

Defense-in-depth: looksSafeSvg() re-checks the markup in the renderer
before inlining, so a compromised manifest still can't inject script.

- iconSvg?: string added to registry entry types (src/types, electron lib,
  preload); dropped the stale logo id field
- BrowseCommunityModal passes iconSvg + kind
- registry test fixture updated to the compiled entry shape (id + iconSvg)

type-check:all + lint + compile clean; 33 registry/oauth tests pass.

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

Caution

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

⚠️ Outside diff range comments (1)
electron/lib/community-registry.test.ts (1)

77-84: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Restore the stubbed global fetch.

vi.restoreAllMocks() restores spies but does not undo vi.stubGlobal("fetch", ...). Add vi.unstubAllGlobals() to the teardown, or enable test.unstubGlobals in vitest.config.ts, so fetch does not stay stubbed between tests.

🤖 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/lib/community-registry.test.ts` around lines 77 - 84, Update the
test teardown in afterEach to call vi.unstubAllGlobals() alongside
vi.restoreAllMocks(), ensuring any fetch stub created by vi.stubGlobal is
removed between tests. Keep the existing temporary-directory cleanup unchanged.
🧹 Nitpick comments (3)
electron/lib/community-registry.test.ts (3)

153-164: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the cached manifest on the 304 path.

Verify that res.manifest contains the previously cached entries and that res.error is undefined; fromCache: true alone does not prove the cached payload was served.

🤖 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/lib/community-registry.test.ts` around lines 153 - 164, Extend the
304 Not Modified test for fetchManifest to assert that res.manifest contains the
previously cached VALID entries and that res.error is undefined, while
preserving the existing fromCache and conditional If-None-Match assertions.

175-181: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Seed the cache before testing refreshManifest().

Because this test starts with an empty cache, a broken non-forced implementation would still call the network and pass. First populate the cache, then call refreshManifest() and assert that it fetches again with fromCache: false.

🤖 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/lib/community-registry.test.ts` around lines 175 - 181, Update the
“refreshManifest forces the network path” test to populate the manifest cache
before invoking refreshManifest, using the existing cache population mechanism
and valid manifest data. Then retain the fetch invocation and fromCache false
assertions to verify refreshManifest bypasses the seeded cache and fetches
again.

93-113: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a regression case for invalid SVG logos.

These tests cover URL and required-field validation, but not the new iconSvg security contract. Add a malformed or unsafe SVG entry and assert it is dropped while valid entries remain.

🤖 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/lib/community-registry.test.ts` around lines 93 - 113, Add a
regression test alongside the existing parseManifest filtering tests that clones
VALID, makes one service’s iconSvg malformed or unsafe, and verifies
parseManifest removes that service while retaining the valid service. Assert the
resulting services length and surviving valid service identity, matching the
existing URL and malformed-entry test style.
🤖 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/plans/sync-lifecycle-hardening.md`:
- Around line 103-110: Expand the durable-tombstone work plan to include desktop
and mobile schema migrations, ownership of the new engine table, and the
read/write adapters used by existing databases. Reference the migration and
adapter components that must create, access, and preserve sync_row_base so Phase
1 has a deployable persistence path.
- Around line 82-93: Define an explicit causal-evidence mechanism for the
delete-wins protocol in docs/plans/sync-lifecycle-hardening.md: specify the
acknowledgement frontier, version vector, or observation token carried by puts
and tombstones, and require reconcileOne to use it to prove a put observed the
delete before resurrecting. At docs/plans/sync-lifecycle-hardening.md lines
134-139, preserve existing HLC values and explicitly prohibit treating
updated_at-derived backfill stamps as authoritative causal evidence.
- Around line 166-171: Update the tombstone-table retention/GC policy to prevent
deletion when a peer’s acknowledged-HLC watermark is missing or no longer
trustworthy after reinstall or app-data loss. Treat newly discovered or
re-registered peers as unsafe until an install-independent identity is
validated, requiring full resync or retaining affected tombstones beyond the
grace period; document this protocol alongside the per-peer watermark and
wall-clock checks.

In `@electron/db/queries.ts`:
- Line 292: Make the deletion update idempotent by restricting the query in
electron/db/queries.ts:292-292 to rows where deleted_at IS NULL, so retries do
not change updated_at or version; update the related test in
electron/db/queries.test.ts:699-705 to verify the second deletion preserves
deleted_at, updated_at, and version.

In `@electron/lib/mcp-oauth.ts`:
- Around line 439-450: Update the server-lifecycle handlers in
electron/ipc/tools.ts that currently call cancelPendingForServer during MCP
server save, deletion, or sign-out to call cancelServerAuth instead, ensuring
both pending deep-link attempts and activeLoopbacks are cancelled. Leave
cancelPendingForServer unchanged for callers that only need to remove deep-link
entries.
- Around line 290-293: Update startServerAuth around cancelServerAuth to guard
concurrent starts per server with a generation token or lock established before
any asynchronous setup. Ensure a newer invocation invalidates or serializes
older attempts, preventing both calls from registering listeners and leaving an
orphaned loopback listener; preserve cancellation of already-registered loopback
and deep-link flows.

In `@src/components/settings/tools/ConnectorLogo.tsx`:
- Around line 63-74: Update the SVG opening-tag replacement in ConnectorLogo’s
icon rendering branch to match every opening form accepted by looksSafeSvg,
including whitespace, tabs, newlines, and no attributes, so width="100%" and
height="100%" are always injected while preserving the existing safety guard.
- Around line 37-45: Update looksSafeSvg’s inline event-handler validation so
handlers are rejected regardless of whether they follow whitespace, a quoted
attribute value, or another attribute delimiter; replace the current
whitespace-dependent pattern with a boundary-aware check such as the suggested
event-attribute detection, while preserving the existing SVG safety checks.

---

Outside diff comments:
In `@electron/lib/community-registry.test.ts`:
- Around line 77-84: Update the test teardown in afterEach to call
vi.unstubAllGlobals() alongside vi.restoreAllMocks(), ensuring any fetch stub
created by vi.stubGlobal is removed between tests. Keep the existing
temporary-directory cleanup unchanged.

---

Nitpick comments:
In `@electron/lib/community-registry.test.ts`:
- Around line 153-164: Extend the 304 Not Modified test for fetchManifest to
assert that res.manifest contains the previously cached VALID entries and that
res.error is undefined, while preserving the existing fromCache and conditional
If-None-Match assertions.
- Around line 175-181: Update the “refreshManifest forces the network path” test
to populate the manifest cache before invoking refreshManifest, using the
existing cache population mechanism and valid manifest data. Then retain the
fetch invocation and fromCache false assertions to verify refreshManifest
bypasses the seeded cache and fetches again.
- Around line 93-113: Add a regression test alongside the existing parseManifest
filtering tests that clones VALID, makes one service’s iconSvg malformed or
unsafe, and verifies parseManifest removes that service while retaining the
valid service. Assert the resulting services length and surviving valid service
identity, matching the existing URL and malformed-entry test style.
🪄 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: e965d915-8db3-4e9d-9e41-759b3f9e7429

📥 Commits

Reviewing files that changed from the base of the PR and between 7af9d0d and 89143a0.

📒 Files selected for processing (19)
  • changelogs/v2.5.6.md
  • docs/plans/sync-lifecycle-hardening.md
  • electron/db/queries.test.ts
  • electron/db/queries.ts
  • electron/lib/community-registry.test.ts
  • electron/lib/community-registry.ts
  • electron/lib/mcp-oauth.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/shared/db-mappers.ts
  • shared/sync/engine.test.ts
  • src/components/settings/AISettings.tsx
  • src/components/settings/AgentSettings.tsx
  • src/components/settings/tools/BrowseCommunityModal.tsx
  • src/components/settings/tools/ConnectorLogo.tsx
  • src/lib/callout-widget.tsx
  • src/lib/livePreview.component.test.tsx
  • src/lib/livePreview.ts
  • src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/components/settings/AgentSettings.tsx
  • src/types/index.ts
  • src/components/settings/AISettings.tsx
  • electron/preload.ts
  • electron/lib/community-registry.ts
  • src/components/settings/tools/BrowseCommunityModal.tsx
  • changelogs/v2.5.6.md

Comment thread docs/plans/sync-lifecycle-hardening.md Outdated
Comment thread docs/plans/sync-lifecycle-hardening.md
Comment thread docs/plans/sync-lifecycle-hardening.md
Comment thread electron/db/queries.ts Outdated
Comment thread electron/lib/mcp-oauth.ts
Comment thread electron/lib/mcp-oauth.ts
Comment thread src/components/settings/tools/ConnectorLogo.tsx
Comment thread src/components/settings/tools/ConnectorLogo.tsx Outdated
ddutchie added 3 commits July 24, 2026 10:13
… guard)

- deleteNote: guard UPDATE with `deleted_at IS NULL` so retries preserve
  deleted_at/updated_at/version; test asserts all three
- tools.ts: MCP save/delete/sign-out now cancelServerAuth (loopback +
  deep-link) instead of cancelPendingForServer (deep-link only)
- mcp-oauth: per-server generation token guards the concurrent-start race
  so a superseded attempt closes its listener instead of orphaning it
- ConnectorLogo: inject width/height for every <svg> form looksSafeSvg
  accepts; boundary-aware inline-event-handler rejection
- community-registry.test: unstubAllGlobals in teardown; stronger 304 +
  refreshManifest cache-bypass assertions
- sync-lifecycle-hardening plan: expand tombstone migrations/adapters,
  causal-evidence mechanism, backfill causality, and stale-peer GC policy
The Configured Servers rows used a generic Server/Globe icon while Browse
Community showed real brand logos. ToolsSettings now fetches the registry
once (cache-first) and maps communityId (== definition name) -> iconSvg +
brandColor, rendering ConnectorLogo for community-installed MCP servers and
services. Manually-added tools (no registry match) keep the lucide fallback;
offline keeps the fallback too.

type-check + lint clean.
19 connectors produced 60 tag chips (50 singletons) — the chip row was
longer than the results. Chips are now the connector's category (fixed
9-item vocabulary from the registry); freeform tags still feed the search
box. Adds category to registry entry types (electron lib + zod, preload,
src/types).

type-check:all + lint + compile clean; 9 registry tests pass.
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