Cordy for Chrome

Changelog·Beta

This page mirrors the product repository changelog and is not a download or publication record. Read the product documentation.

v2.4.5Latest source entry2026-09-10

Chat composer

  • Keep the composer editable while a reply is generating. Enter parks the message in a single-slot queue (re-queuing replaces it) that is sent automatically when the reply ends; the queue chip above the composer offers Send now (interrupts the current reply first) and Remove. While generating, the send control splits into Queue and Stop.
  • Sends that carry @-mentions, such as /summarize-tabs, now show a preparing state and lock the send button and composer while page content is being read. A single-tab read times out after 4 seconds and degrades to metadata only.
  • Add the /solo [on|off] command (alias /single-turn) to toggle single-turn mode for the current conversation tab, kept in sync with the toolbar icon. Fix system commands such as /clear and /solo being sent to the model as ordinary prompts when an @-mention chip was attached.

Stability

  • Introduce a turn-boundary primitive: a queued send is dispatched only after the previous turn has finished and persisted, and tool-approval resumes and regenerations are tracked the same way. Send and stop messages carry a sequence number, so a late stop can no longer kill a newer reply.
  • The Stop button in a passively synced side panel (one that did not start the reply) now actually stops the background generation.

Performance

  • Virtualize the Tabs → History list so typing in its search box no longer re-renders hundreds of rows; debounce history queries and the bookmark grid/tag search by 200 ms.
v2.4.42026-09-10

Sidebar navigation

  • Keep primary navigation in a fixed order and highlight the current page. Show the active page name to the right of its icon, with conversation history remaining under Chat.
  • Restore compact icons, buttons, and spacing. Reveal only the active label without scaling the icon or button, with keyboard, RTL, and reduced-motion support.
  • Connect the active entry to its page toolbar with a continuous vector contour. Fix corner seams when zoomed and keep hover backgrounds below the border.
  • Prevent closing tooltips with hidden anchors from briefly widening the page during navigation, while preserving normal tooltip exit animations.
  • Place Current, History, Read Later, and Collections icons on the right of the page toolbar, using an underline for the selected subpage. Show sidebar counts as a number and the corresponding icon.

Themes and fonts

  • Add appearance settings to both Sidepanel and Dashboard, with independent light and dark themes and system mode. Save choices immediately and synchronize them across pages.
  • Include eight themes: Classic Light, Warm Paper, Catppuccin Latte, Classic Dark, Ember, Catppuccin Mocha, Nord, and Dracula.
  • Offer System Default, LXGW Neo XiHei, LXGW WenKai, and Frex Sans. Bundle the three fonts for offline loading with their complete licenses.
  • Apply the selected font to interface and prose while keeping code monospace and preserving existing font sizes, line heights, and navigation dimensions. Keep new-tab wallpapers independently configurable.
  • Add contextual ? help and an appearance-only reset. Cover all 12 languages, narrow layouts, and theme/font backup restoration.
v2.4.32026-09-08

Republished on 2026-09-09 to complete data-cleanup and UI tasks omitted from the initial release.

🔖 Data cleanup & UI completion

  • Data Management preserves bookmarks: full and selective cleanup no longer delete bookmark data or Chrome bookmarks. Remove the bookmark-cleanup option and backend capability. Individual bookmark-manager operations remain available; API-key deletion remains a separate choice.
  • Fix destructive-button text colors and allow application-data cleanup when there are no chat messages. Correct propagation of the API-key deletion choice to the service. Confirmation text and privacy documentation explicitly describe bookmark preservation.
  • Split sidebar global icons and Current / History / Read Later / Collections into two rows, with four equal-width tabs so the last tab remains reachable in a narrow sidebar.
  • Consolidate bookmark-sidebar tools into stable Refresh, View, and More buttons, with tree / tag / grid selection and contextual sort or collapse operations in menus.
  • Apply Cordy's light, dark, and system theme colors to the command palette and show the local logo in its search row. Prevent the Escape keyup event from reaching the host page after dismissing the palette.
  • Add page headings to Overview, Search, AI Management, Browsing History, Read Later, Bookmarks, and Settings, preserving existing Collections, Translation, and Conversation History headings.

🎛️ AI settings

  • Replace stacked provider forms with a searchable provider directory and one active editor, plus a separate Feature Models tab. Preserve secure key storage, saving on blur, connection checks, and custom model configuration.
  • Remove the redundant AI heading and introductory text. Add contextual ? help for credentials, connection checks, API URLs, and model selection, with keyboard dismissal, focus restoration, all 12 languages, and narrow RTL layouts.

💬 Answer versions & conversation forks

  • Generate, preview, and switch between answer versions while preserving historical source chains. Continue from an answer or edit a prompt to create an independent conversation fork with isolated context.
  • Preserve answer placement through foreground and background saves and stream recovery. Stop generation without activating an incomplete candidate, and find inactive answers through search.
  • Show cloud privacy consent when a chat panel first opens on a cloud model. When no message is waiting, the confirmation button reads "Agree".

🌐 Translation & local AI

  • Persist the translation runtime, source and target languages, and last cloud and browser-local model selections across closing the page and switching between local and cloud runtimes.
  • Remove input-length-based output caps from cloud translation to avoid truncating reasoning-model responses. Show failure reasons with affected segment numbers and record them in background logs.
  • Derive the Gemma translation generation cap from the loaded model's context window and actual prompt length, replacing the fixed 1,024-token limit.

⌨️ Interaction & reliability

  • Isolate keyboard events from the command palette, selection menu, and floating cards so typing does not trigger host-page shortcuts. Fix keyboard input in the model picker's portaled search field.
  • Prevent clicks from passing through a closing command menu to buttons underneath, and keep keyboard selection and pointer hover from highlighting two commands at once.
  • Register keyboard-command listeners synchronously during background startup. Explain unsupported pages or missing site access in Settings when the palette cannot open, and use translated shortcut names in help.
  • Fix nested-markup sanitization in selection results, floating-card positioning and narrow layouts, and drag, resize, and keyboard-focus interactions.
  • Upgrade to AI SDK 7.0.93, Vitest 5, and related dependencies, with updated stream-error reporting and regression coverage.
v2.4.22026-09-04

Since v2.4.1: 95 commits · 706 files · +80,691 / −13,948. Three threads: 🎛️ a unified model picker (ModelPicker) with provider state as a single source of truth · 🛡️ 100+ defects from the 2026-08-17 modernization plan closed one by one (several security- and data-loss-class) · 📦 install package 50 MB → 32 MB, React removed from the service worker entirely.

⚠️ Breaking: minimum_chrome_version raised from 142 to 148 (Chrome's built-in Prompt API only fully lands in 148). Chrome 142–147 cannot install this release.

⚠️ User-visible behaviour changes: auto-cleanup defaults to "notify only" (was "delete") and has a switch; "Delete all data" now really deletes the bookmark mirror / saved tab groups / logs / AI settings, with API keys as a separate checkbox; deleting bookmarks (health page / sidebar) always deletes from Chrome too; dead-link detection sends zero requests to sites you have not granted; after revoking site access, orphaned content scripts on open pages can no longer reach cloud AI / TTS / page content; translation slots no longer offer local gateways (Ollama / LM Studio).

🎛️ Unified model picker & providers

  • One ModelPicker: the four Dashboard text slots, Sidepanel settings, the translation workbench and the chat composer share one two-level picker; provider/source status, catalog errors/retry, browser-built-in and local-native models share one data boundary (useModelPickerData), with strings in all 12 languages. The old model-selector.tsx and the TTS fallback are gone. Design and verification scope: docs/reviews/2026-09-02-model-picker-unification.md.
  • Consistent provider status: built-in and custom provider cards share SourceStatusBadge and one status resolver; probe exclusion is a set, so a stale "checking" no longer locks Detect forever; ghost-key problems outrank the short-lived health cache; writing a key invalidates the health cache immediately. Built-in providers use vendored brand logos.
  • Pricing unified to USD per 1M tokens: ModelInfo.pricing (USD/1K + unit) becomes ModelInfo.pricePerMTokens, so picker prices are no longer off by 1000x; contextWindow 0 is normalised; anthropic / google / xai discovery is now authenticated. Old modelCache rows are treated as stale and refetched. The first time you open the model list after upgrading, prices are briefly missing until the catalog refreshes.
  • AI-settings catalog tri-state + revision sentinel: provider-keyed idle / loading / ready, one validator shared by snapshot and migration, a single slot writer so concurrent writes cannot trample each other.
  • local-native: an endpoint change resets discovery; network-class failures keep the known model list.

🌐 Translation

  • Same-language passthrough: when source = target (including auto-detect landing on the target), the text passes through as-is instead of Chrome Translator refusing to create a translator or a cloud/local model being billed for an identity rewrite; zh-CN <-> zh-TW is explicitly excluded.
  • Auto-swap on language collision + swap button; native <select> replaced by base-ui Select; source/translation become a draggable split (side by side on wide viewports, stacked on narrow, ratio persisted, double-click to reset).
  • Workbench moved to ModelPicker with slot state as the source of truth, write exclusion and fail-closed local gateways; target language no longer hard-coded to zh-CN; zh-CN / zh-TW no longer collapsed into one zh; one failed block no longer sinks the whole document; output no longer silently truncated; tasks no longer orphaned.

🔖 Bookmarks & collections

  • Saved-page indicator: a per-tab badge shows saved state (SPA routes included); the sidebar current-page bar covers save / undo / multiple hits / local-only / orphan fallback; localBookmarks gains a normalizedUrl index (Dexie v5, backfilled automatically).
  • Sidebar bookmark tree: headless-tree + virtualisation, expansion persisted by stable key, keyboard navigation and RTL mirroring.
  • Faster collections start page: preloading runs serially in idle time after the route chunk resolves; favicons use the local _favicon endpoint (no network, works offline); expansion persisted, newly saved cards scrolled into view and highlighted; e2e asserts zero non-extension requests on the page.
  • Full sync only rebuilds the Chrome mirror and runs in one transaction; sync history no longer grows without bound; save / restore no longer silently drops links; JSON import no longer flattens nested folders into the root; closing tabs after saving is the user's call; bookmark and chat sync no longer trample each other; cross-root navigation no longer silently no-ops.

💬 Chat panel

  • User messages render verbatim (no Markdown line folding, no swallowed <think> text); /clear resets in place instead of jumping to the far right; slash-command menu candidates completed.
  • Follow-ups and regenerate no longer summarise a page that is not visible; deleting a mid-thread message no longer breaks the chain, backups carry the root-level branch choice; "continue in chat" goes through the canonical write path so replies are no longer invisible forever; attachment-only sends no longer vanish; Enter submits its own session's form; the @-mention popup sends exactly what it says it sends.
  • The model can finally see which tools it called (tool-call / tool-result backfilled at the transport layer); inline AI cards no longer spin forever and the stop button survives a service-worker rebuild.
  • Panel open/closed state uses native sidePanel.onOpened / onClosed instead of inference.

🧠 Local AI & TTS

  • @browser-ai/transformers-js removed; Chrome's four built-in AI globals typed via @types/dom-chromium-ai with a self-implemented provider; three hard local-native defects fixed; Chrome AI initialisation covers all four APIs.
  • Local chat emits progress frames while loading, so the watchdog no longer mistakes a cold start for a dead stream.
  • Kokoro fixes: the voice-pack byte check was wrong (511 vs the real 510 rows) and had been rejecting every Kokoro read; long text without a full stop no longer cuts out midway; audio finally stops (content-side button + background orphan runs); cloud TTS no longer spins forever.

🛡️ Security & privacy

  • After revoking site access, orphaned content scripts can no longer reach cloud AI (new requireHostAccess gate over selection toolbar / inline AI / page-content read / content-side TTS).
  • Content scripts can no longer read the in-progress conversation; URLs read by tools are redacted, not only @-mentions; a missing master key errors honestly instead of silently minting a new one.
  • The destructive-op token docstring no longer claims "double protection"; inline payloads have a real upper bound; dead-link detection makes no requests to unauthorised sites.

🗄️ Storage & data

  • Auto-cleanup gets guard rails: default "notify only", can be switched off, delete mode reports what it removed; it previously used origin-level usage (including gigabytes of local model weights) as its stop condition and would empty the whole chat history.
  • "Delete all data" covers the same 5 storage domains as exportAllData; /clean no longer silently clears site data, and command failures are visible.
  • Logging: redactSensitive gains an Error branch, restoring error messages at 318 call sites; exported logs carry the error message; history search keeps the newest hits and says when it truncated.

📦 Performance & package size

  • ORT switched to the jspi variant (-9 MB) and shiki to an allowlist of 19 languages + 2 themes (another -9 MB): install package 50 MB → 32 MB; unreferenced jsep.mjs removed.
  • React and the React build of zustand pulled out of the service worker's dependency graph (5 stores on vanilla); the SW sheds 73 KB of dead providers and three remote URLs; the 45 KB model-window table moved off the sidepanel's first screen.
  • The build-time modulepreload policy is wired into Vite for the first time, guards only chunks that must stay lazy, and is backed by a post-build static-closure invariant test.

🎨 UI & i18n

  • Content-script font-size setting finally takes effect; Arabic direction reaches base-ui; wallpaper clicks respond; empty @tab queries return results; toasts no longer have a white background.
  • The two i18n runtimes converge into one; internal English error strings are no longer rendered to users; the store-review docs and settings page no longer describe features that do not exist.
  • Tailwind v3 plugins and two framework-fighting patterns removed.

🧰 Engineering, gates & dependencies

  • Gates: the size budget evaluates the freshly built output; the chrome boundary uses native oxlint rules; the silent-failure ratchet sees comment-only catch bodies; oxfmt is part of the gate; exhaustive-deps at zero and promoted to error, react-compiler under a --max-warnings ratchet; the coverage ratchet and two more checks run in CI, check:docs added to the release path; 17 colocated specs type-checked; pure-logic tests run in the node environment.
  • The "obfuscated build" that never obfuscated is removed; three analyze tools collapsed into one; .gitattributes pins LF; supply-chain publish-delay policy configured explicitly.
  • Dependencies: 32 packages across two batches (shiki 4, oxlint 1.79 with the split react-compiler plugin, oxfmt 0.63, ...); two verbatim-identical EventEmitters merged.
  • Docs: the 2026-08-17 modernization plan with its per-item ledger, the 86-item re-verification, the three bookmark specs, and the model-picker unification plan with three review rounds are archived under docs/reviews/.
v2.4.12026-08-13

A six-stage hardening pass: 1,668 → 2,847 tests, 55 new real-browser E2E specs, coverage 41.8% → 59.2% (the old 56% figure was inflated — see below). Around 40 defects fixed, three of them data-loss class.

⚠️ Four user-visible behaviour changes: bookmark cleanup no longer deletes links that are merely unreachable; "delete bookmarks" now also deletes them from Chrome and syncs that to all your devices; the command palette requires a real user gesture before it will fetch anything; OPEN_URL rejects addresses outside its allowlist.

🌐 Twelve languages are now properly supported: ~5,500 strings that had been falling back to English are translated, and the layout uses logical direction properties so Arabic gets real RTL.

🚨 Data safety (three irreversible defects)

  • Offline, "one-click cleanup" offered to delete the entire bookmark library. The health check separates dead (the server answered 4xx/5xx) from timeout/error (could not be reached — which is what offline, CORS and rate limiting produce for perfectly live links), but the cleanup dialog received all three and pre-selected everything. Scan → cleanup → confirm was four clicks from an empty library, with a green "cleaned N items" as the only feedback. Only confirmed-dead links reach the dialog now; unreachable ones stay visible in the table and remain individually removable.
  • Backup restore was entirely broken. Import validation required message.id to be a string when the real primary key is a number, so any backup containing chat history failed to restore — a feature that exists precisely for users who have already lost data.
  • The cleanup dialog never pre-selected anything, and fixing that alone would have deleted every copy of each duplicate. Seeding lived in a callback a controlled dialog never fires, so Confirm was permanently disabled; and findDuplicates returns EVERY bookmark sharing a URL, so select-all would have left the user with none.

🛡️ Security

  • Bookmarks, browsing history and open tabs were readable by any script on any granted site, with no user gesture. The command palette's six fetch messages are legitimately sent by a content script and therefore cannot use the extension-page sender gate — and nothing else checked that the palette had ever been opened. The palette can only be opened by a Chrome keyboard command routed through the background, which is an unforgeable gesture signal, so the background now issues a short-lived per-tab session at exactly that moment. Session state lives in chrome.storage.session, not an in-process map (the worker is evicted after ~30s idle), and the TTL is absolute rather than sliding, so a compromised page cannot keep one gesture alive by polling.
  • After revoking site access, an orphaned content script could keep reading bookmarks and history. Chrome stops future injections but does not kill the already-injected instance. Host access is now a precondition of the session.
  • A prompt-injection escape. Marker neutralization matched two literal strings, so case variants, internal whitespace, attributes and zero-width characters passed through — any page (or a bookmark title) could close the untrusted-data region early.
  • OPEN_URL accepted any address, including chrome:// internals, file:// and javascript:. SWITCH_TO_TAB could focus any tab in any window and could be used to probe how many tabs the session had opened.

🐛 Fixes

  • Switching models mid-conversation collapsed the context budget to 10K, silently truncating what was actually sent.
  • A keep-alive alarm never stopped. After the worker was evicted mid-stream, chrome.alarms survived (the browser owns it) while the in-process map that decides whether keep-alive is needed did not — so a 30-second alarm woke the worker and broadcast a heartbeat to every tab, permanently.
  • Local TTS replayed another tab's audio; background runs never released after the offscreen producer died.
  • Streams could end as blank "successes" (200 with an empty or non-conforming body), half-open SSE streams truncated silently, and a cloud error with a non-JSON body rendered no error banner at all.
  • The local runtime's seven diagnosable failure states (not running, wrong URL, no model…) only ever reached the dashboard's detect button; chat showed a generic failure.
  • Bookmark de-duplication failed for any URL whose query contained ~ ( ) ! ' * or an encoded space — stripping a tracking parameter re-encoded the whole query, so the same page with and without utm_* produced different keys.
  • Pasting a large document froze the side panel (quadratic backtracking in the Markdown link parser, run synchronously on every keystroke).
  • Copy → paste-import dropped links whose title contained a line break.
  • A backward clock made two TTLs never expire (elapsed time went negative, and a negative number is never >= TTL), one of which guards the delete-all-data confirmation token.

✨ Added

  • Editing a saved tab finally works. The entry point had shipped in three places since v2.3 and answered every click with "coming soon"; the service layer had no way to update a saved tab at all.
  • Batch actions in collections and read-later are reachable — selection mode previously had no entry point, so the whole batch code path was dead.

🌐 Internationalization

  • ~5,500 strings translated; twelve languages no longer mix in English.
  • Real RTL for Arabic: nothing in the tree had ever set dir and the HTML entry hardcoded lang="zh-CN". Direction now follows the active language and the layout uses logical properties so it actually mirrors.

🧹 Platform consistency

  • Removed ConnectionTracker's setInterval heartbeat (MV3 hard rule 3, and silence never meant a dead port anyway); deleted 183 lines of self-declared parked dead code; cleared the dead fields and Dexie index left by the bookmark-AI removal.
  • New Dexie v13 / v14: v9 had declared a UNIQUE index over a column existing rows already carried, so a profile holding duplicates aborted the upgrade and could never be opened again — taking chats, settings and API keys with it — and because Dexie builds a version's indexes before running its callback, no later version could repair it. v13 de-duplicates, v14 restores the constraint.

🧪 Testing and engineering

  • pnpm gate: an eight-stage local merge gate (three typechecks, lint, docs, full suite with coverage, build, real-browser E2E), ~5 minutes; --fast runs in ~20 seconds.
  • A real MV3 E2E harness (Playwright, throwaway profile per run), 55 specs. MV3 hard rules 1 and 3 had been comment-only discipline; they now have guards.
  • The coverage gate had never run in five months: the config used Jest's thresholds.global syntax, which vitest reads as a glob that matches nothing (verified: coverage at 0% still exited 0). entrypoints/** — 47% of the source — was also outside the measurement, so the old numbers were inflated by 12–16 points.
  • Large-dataset fixtures and pnpm bench baselines; two history hot paths optimized against them (100k-row sort 47–53ms → 25–28ms; domain grouping 14.0ms → 0.33ms).

🐛 Fixes (detail)

  • Switching models mid-conversation no longer collapses the context budget to 10K. The model context window the budget needs had exactly one source: the ai-settings store's availableModels — a volatile, provider-scoped, network-populated cache. Switching provider emptied it, and saveSettings also deleted the Dexie cache row for the provider being switched to, which is precisely the row loadSettings restores from, so reopening the side panel stayed cold as well. With a cold cache the window resolved to undefined and assembleChatContext fell back to its 10,000-token default: the usage ring showed with a fixed 4.5K reserve, and real sends were silently truncated — not merely the gauge. The selected model's window / max output tokens are now stamped onto the model slot at selection time (saveSettings is the single choke point every model-picker funnels through), and the budget resolves in three tiers: live catalog → stamped slot value → vendored LiteLLM table. The incoming provider's model cache is no longer deleted (staleness is already handled by the read path's baseUrl check). The conservative 10K fallback still applies when no window is known at all.
  • The collection "⋯" menu no longer jumps to the top-left corner on hover. The action bar holding the trigger was only display:flex under group-hover / group-focus-within, while the menu popup is portalled to body. Moving the pointer onto a menu item lost both hover and focus-within (focus lives outside the DOM subtree), so the bar went display:none, the anchor collapsed to zero size, and floating-ui re-anchored the popup at the viewport origin. The bar is now pinned open while the menu is open, and released only once the exit animation completes (onOpenChangeComplete).

📝 Documentation

  • Docs reconciled with the code. The canonical documents (ARCHITECTURE / AI_ENGINEERING / MV3_PLATFORM_CONTRACT) still described the deleted agent framework (AgentRegistry / AgentExecutor / agent-handlers) as current; the roadmap listed the already-delivered Phase 2/3 as future work while contradicting the connector retirement; FEATURE_AUDIT and BOOKMARKS_DESIGN marked removed bookmark AI features as "implemented". All rewritten against the actual code, and four agent-framework documents archived.